数据库的休眠加密对应用程序完全透明
Posted
技术标签:
【中文标题】数据库的休眠加密对应用程序完全透明【英文标题】:Hibernate Encryption of Database Completely Transparent to Application 【发布时间】:2010-12-02 00:39:07 【问题描述】:我正在处理一个 Grails 1.0.4 项目,该项目必须在不到 2 周的时间内发布,而客户刚刚提出了一个要求,即数据库中的所有数据都应该加密。
由于对应用程序本身的每个数据库访问进行加密可能会花费大量时间并且容易出错,因此我寻求的解决方案是某种对应用程序透明的加密。
有没有办法设置 Hibernate 来加密所有表中的所有数据(可能除了 id 和 version 列)或者我应该寻求 mysql 解决方案(我们使用的是 MySQL 5.0)?
编辑: 感谢您所有关于替代解决方案的帖子,如果客户改变主意,那就太好了。就目前而言,要求是“数据库中没有纯文本”。
我想指出的第二件事是,我正在使用 Grails,对于那些不熟悉它的人来说,它是一种约定优于配置,因此即使对应用程序进行不符合约定的微小更改也应避免。
【问题讨论】:
请确保向客户指出这将对您应用程序中的所有内容的性能产生影响。我们只加密我们合法必须的数据,如 SSN 和税号,当我们需要插入这些字段或读取它们时,它仍然会减慢速度。对每个表中的每个字段都这样做会极大地影响性能。如果他们想接受这种打击是可以的,但要让他们知道这可能会使网站速度慢得用户无法接受。在您这样做之前让他们知道,并让他们以书面形式签署,以保护您的公司。 在不到 2 周的时间内完成这项工作并进行全面测试非常幸运。这确实是一个长达数月的项目,因为它彻底改变了您的应用程序的一切。 这听起来是个非常糟糕的主意。想想索引和搜索数据库时的所有影响! 【参考方案1】:如果您在应用程序中完成工作,您可以使用 Hibernate 自定义类型,它不会对您的代码添加那么多更改。
这是我使用过的加密字符串自定义类型:
import org.hibernate.usertype.UserType
import org.apache.log4j.Logger
import java.sql.PreparedStatement
import java.sql.ResultSet
import java.sql.SQLException
import java.sql.Types
class EncryptedString implements UserType
// prefix category name with 'org.hibernate.type' to make logging of all types easier
private final Logger _log = Logger.getLogger('org.hibernate.type.com.yourcompany.EncryptedString')
Object nullSafeGet(ResultSet rs, String[] names, Object owner) throws SQLException
String value = rs.getString(names[0])
if (!value)
_log.trace "returning null as column: $names[0]"
return null
_log.trace "returning '$value' as column: $names[0]"
return CryptoUtils.decrypt(value)
void nullSafeSet(PreparedStatement st, Object value, int index) throws SQLException
if (value)
String encrypted = CryptoUtils.encrypt(value.toString())
_log.trace "binding '$encrypted' to parameter: $index"
st.setString index, encrypted
else
_log.trace "binding null to parameter: $index"
st.setNull(index, Types.VARCHAR)
Class<String> returnedClass() String
int[] sqlTypes() [Types.VARCHAR] as int[]
Object assemble(Serializable cached, Object owner) cached.toString()
Object deepCopy(Object value) value.toString()
Serializable disassemble(Object value) value.toString()
boolean equals(Object x, Object y) x == y
int hashCode(Object x) x.hashCode()
boolean isMutable() true
Object replace(Object original, Object target, Object owner) original
基于此,为 int、long 等创建类似的类应该很简单。要使用它,请将类型添加到映射闭包:
class MyDomainClass
String name
String otherField
static mapping =
name type: EncryptedString
otherField type: EncryptedString
我省略了 CryptoUtils.encrypt() 和 CryptoUtils.decrypt() 方法,因为这不是 Grails 特有的。我们正在使用 AES,例如“密码密码 = Cipher.getInstance('AES/CBC/PKCS5Padding')”。无论您最终使用什么,请确保它是双向加密,即不要使用 SHA-256。
【讨论】:
【参考方案2】:如果客户担心有人带着硬盘走开,那么使用像 Truecrypt 这样的全盘解决方案应该可以解决问题。如果担心流量被嗅探,请查看this 部分关于 ssl over JDBC 的 mysql 文档。请记住,如果有人破坏了您的服务器,所有赌注都将被取消。
【讨论】:
【参考方案3】:客户可以轻松做到这一点,而无需更改您的应用程序中的任何内容。
首先,在mysql层开启SSL,或者使用SSH隧道,对服务器之间的通信进行加密。
其次,将 mysql 数据库存储在加密卷上。
任何可能暴露 mysql 数据库文件系统或登录 mysql 服务器所需凭据的攻击都无法通过加密数据得到缓解,因为同样的攻击可用于从应用程序本身检索加密密钥。
【讨论】:
【参考方案4】:好吧,我已经很久没有问这个问题了。同时,感谢所有的答案。他们在处理加密整个数据库的原始想法时很棒,但要求更改为只加密敏感的用户信息,如姓名和地址。所以解决方案类似于下面的代码。
我们已经实现了一个 Encrypter,它从记录中读取加密方法(因此每条记录可以有不同的加密),并使用它将瞬时重复字段连接到数据库中加密的字段。额外的好处/缺点是:
数据也在内存中加密,因此每次访问 getFirstName 方法都会对数据进行解密(我猜有一种方法可以缓存解密的数据,但在这种情况下我不需要它)加密字段不能与用于搜索数据库的默认 grails/hibernate 方法一起使用,我们在服务中创建了自定义方法来获取数据,对其进行加密,然后在查询的 where 子句中使用加密数据。使用 User.withCriteria 很容易
类用户
byte[] encryptedFirstName
byte[] encryptedLastName
byte[] encryptedAddress
Date dateCreated // automatically set date/time when created
Date lastUpdated // automatically set date/time when last updated
EncryptionMethod encryptionMethod = ConfigurationHolder.config.encryption.method
def encrypter = Util.encrypter
static transients = [
'firstName',
'lastName',
'address',
'encrypter'
]
static final Integer BLOB_SIZE = 1024
static constraints =
encryptedFirstName maxSize: BLOB_SIZE, nullable: false
encryptedLastName maxSize: BLOB_SIZE, nullable: false
encryptedAddress maxSize: BLOB_SIZE, nullable: true
encryptionMethod nullable: false
// constraints
String getFirstName()
decrypt('encryptedFirstName')
void setFirstName(String item)
encrypt('encryptedFirstName',item)
String getLastName()
decrypt('encryptedLastName')
void setLastName(String item)
encrypt('encryptedLastName',item)
String getAddress()
decrypt('encryptedAddress')
void setAddress(String item)
encrypt('encryptedAddress',item)
byte[] encrypt(String name, String value)
if( null == value )
log.debug "null string to encrypt for '$name', returning null"
this.@"$name" = null
return
def bytes = value.getBytes(encrypter.ENCODING_CHARSET)
def method = getEncryptionMethod()
byte[] res
try
res = encrypter.encrypt( bytes, method )
catch(e)
log.warn "Problem encrypting '$name' data: '$string'", e
log.trace "Encrypting '$name' with '$method' -> '$res?.size()' bytes"
this.@"$name" = res
String decrypt(String name)
if(null == this.@"$name")
log.debug "null bytes to decrypt for '$name', returning null"
return null
def res
def method = getEncryptionMethod()
try
res = new String(encrypter.decrypt(this.@"$name", method), encrypter.ENCODING_CHARSET )
catch(e)
log.error "Problem decrypting '$name'", e
log.trace "Decrypting '$name' with '$method' -> '$res?.size()' bytes"
return res
【讨论】:
【参考方案5】:另一种选择是使用 JDBC 驱动程序,以两种方式即时加密/解密数据。请记住,任何解决方案可能都会使加密字段的搜索无效。
恕我直言,最好的解决方案是 longneck 提出的解决方案,它将使一切变得更容易,从管理到开发。此外,请记住,任何具有客户端加密的解决方案都会使您的所有数据库数据在客户端之外无法使用,即您将无法使用 jdbc 客户端或 MySQL 查询浏览器等好的工具。
【讨论】:
" 请记住,客户端加密的任何解决方案都会使您的所有 db 数据在客户端之外无法使用" 好点【参考方案6】:Jasypt 与 Hibernate 集成:http://jasypt.org/hibernate3.html。但是,不能使用使用 WHERE 子句的查询
【讨论】:
使用固定盐时可以使用 WHERE 子句进行查询【参考方案7】:除非您打算为所有类声明自定义 CRUD 并在查询中手动加密它们,否则生成的 id、版本、映射外键(基本上由 Hibernate 维护的所有内容)都已失效。
对于其他一切,您有几个选择:
@PostLoad
和 @PrePersist
entity listeners 将负责所有非查询操作。
实现自定义 String / Long / Integer / etc... 类型来处理加密将同时处理查询和 CRUD 操作;但是映射会变得相当混乱。
您可以围绕 JDBC 驱动程序(以及 Connection / Statement / PreparedStatement / ResultSet / 等)编写一个瘦包装器来为您进行加密。
就查询而言,您必须手动处理加密(除非您使用上面的 #2),但您应该能够通过单个入口点来执行此操作。我不确定 Grails 如何(或是否)处理这个问题,但是使用 Spring,例如,它就像扩展 HibernateTemplate 一样简单。
【讨论】:
以上是关于数据库的休眠加密对应用程序完全透明的主要内容,如果未能解决你的问题,请参考以下文章
SQL Server安全(9/11):透明数据加密(Transparent Data Encryption)