Mysql 数据库的Hibernate加密对应用程序完全透明
我正在从事一个Grails1.0.4项目,该项目必须在不到两周的时间内发布,而客户刚刚提出了一个要求,即数据库中的所有数据都应该加密 由于对应用程序本身中的每个数据库访问进行加密可能需要大量时间,并且容易出错,因此我寻求的解决方案是某种对应用程序透明的加密 有没有办法设置Hibernate来加密所有表中的所有数据(除了id和version列),或者我应该寻找一个MySQL解决方案(我们使用的是MySQL 5.0) 编辑: 感谢您所有关于替代解决方案的帖子,如果客户改变主意,那就太好了。就目前而言,要求是“数据库中没有纯文本”Mysql 数据库的Hibernate加密对应用程序完全透明,mysql,database,hibernate,encryption,grails,Mysql,Database,Hibernate,Encryption,Grails,我正在从事一个Grails1.0.4项目,该项目必须在不到两周的时间内发布,而客户刚刚提出了一个要求,即数据库中的所有数据都应该加密 由于对应用程序本身中的每个数据库访问进行加密可能需要大量时间,并且容易出错,因此我寻求的解决方案是某种对应用程序透明的加密 有没有办法设置Hibernate来加密所有表中的所有数据(除了id和version列),或者我应该寻找一个MySQL解决方案(我们使用的是MySQL 5.0) 编辑: 感谢您所有关于替代解决方案的帖子,如果客户改变主意,那就太好了。就目前而言
我想指出的第二件事是,我使用的是Grails,对于那些不熟悉Grails的人来说,这是一种约定而非配置,所以即使是不按约定对应用程序进行的小更改也应该避免,映射外键(基本上是由Hibernate维护的所有外键)将被删除,除非您打算为所有类声明自定义CRUD并在查询中手动加密它们 除此之外,您还有两种选择:
@PostLoad
和@PrePersist
将处理所有非查询操作李>
就查询而言,您必须手动处理加密(除非您使用上面的#2),但您应该能够通过单个入口点进行处理。我不确定Grails是如何(或是否)处理这个问题的,但是使用Spring,例如,它就像扩展HibernateTemplate一样简单。如果您在应用程序中完成了这项工作,您可以使用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 }
}
我省略了CryptoUtils.encrypt()和CryptoUtils.decrypt()方法,因为这不是Grails特有的。我们使用的是AES,例如“Cipher Cipher=Cipher.getInstance('AES/CBC/PKCS5Padding')”。无论您最终使用什么,请确保它是双向加密的,即不要使用SHA-256。客户可以轻松做到这一点,而无需更改应用程序中的任何内容 首先,通过在mysql层打开SSL或使用SSH隧道来加密服务器之间的通信 其次,将mysql数据库存储在加密卷上
任何可能暴露mysql数据库文件系统或登录mysql服务器所需凭据的攻击都不能通过加密数据来缓解,因为相同的攻击可用于从应用程序本身检索加密密钥。另一种选择是使用JDBC驱动程序动态加密/解密数据,双向的。请记住,任何解决方案都可能使加密字段的搜索无效
我认为最好的解决方案是由长颈公司提出的,它将使从管理到开发的一切变得更加容易。此外,请记住,任何客户端加密的解决方案都会使您的所有db数据在客户端之外无法使用,也就是说,您将无法使用像jdbc客户端或MySQL查询浏览器这样的好工具,等等。如果客户担心有人会带着硬盘离开,那么使用完整的磁盘解决方案,例如,应该可以。如果有人担心流量被嗅探,那么请看一看关于JDBC上ssl的部分mysql文档。请记住,如果有人破坏了您的服务器,所有赌注都将被取消。Jasypt与Hibernate集成:。但是,使用WHERE子句的查询不能使用我已经很久没有问这个问题了。同时,谢谢你的回答。在处理加密整个数据库的原始想法时,它们非常棒,但要求只加密敏感的用户信息,如姓名和地址。因此,解决方案类似于下面的代码 我们已经实现了一个加密程序,它从记录中读取加密方法(因此每个记录可以有不同的加密),并使用它将临时重复字段连接到数据库中加密的字段。增加的优点/缺点包括:
- 数据也在内存中加密,因此每次访问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 }
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
}