数据库的Hibernate加密对应用程序完全透明

时间:2009-09-28 14:44:39

标签: mysql database hibernate encryption grails

我正在开发Grails 1.0.4项目,该项目必须在不到两周的时间内发布,客户刚刚提出要求数据库中的所有数据都应加密。

由于应用程序本身对每个数据库访问的加密可能需要花费大量时间并且容易出错,因此我寻求的解决方案是对应用程序透明的某种加密。

有没有办法设置Hibernate来加密所有表中的所有数据(idbie和version列除外)或者我应该寻求MySQL解决方案(我们使用的是MySQL 5.0)?

编辑: 感谢您提供替代解决方案的所有帖子,如果客户改变了想法,那就太棒了。至于现在,要求是“数据库中没有纯文本”。

我要指出的第二件事是我正在使用Grails,对于那些不熟悉Grails的人来说,这是一个关于配置的约定,所以即使应用程序的小变化也应该避免。< / p>

7 个答案:

答案 0 :(得分:5)

如果您在应用程序中完成工作,则可以使用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 cipher = Cipher.getInstance('AES / CBC / PKCS5Padding')”。无论你最终使用什么,请确保它是双向加密,即不要使用SHA-256。

答案 1 :(得分:4)

如果客户担心有人在物理上走开硬盘驱动器,那么使用像Truecrypt这样的完整磁盘解决方案应该可行。如果担心流量被嗅探,那么请查看ssl上的this部分mysql文档。请记住,如果有人妥协您的服务器,所有投注都将被取消。

答案 2 :(得分:2)

客户可以轻松完成此操作,而无需更改应用程序中的内容。

首先,通过在mysql层中启用SSL来加密服务器之间的通信,或者使用SSH隧道。

第二,将mysql数据库存储在加密卷上。

任何可以暴露mysql数据库的文件系统或登录到mysql服务器所需凭据的攻击都不会通过加密数据来缓解,因为可以使用相同的攻击从应用程序本身检索加密密钥。 / p>

答案 3 :(得分:2)

自从我提出这个问题以来已经很久了。与此同时,感谢所有答案。在处理加密整个数据库的最初想法时,它们非常棒,但要求更改为仅加密敏感用户信息,如名称和地址。所以解决方案就像下面的代码一样。

我们已经实现了一个Encrypter,它从记录中读取加密方法(因此每条记录可以有不同的加密),并使用它将瞬时重复字段连接到数据库中加密的字段。额外的奖励/缺点是:

  • 数据也在内存中加密,因此每次访问方法getFirstName都会解密数据(我猜有一种方法可以缓存解密数据,但在这种情况下我不需要它)
  • 加密字段不能与默认的grails / hibernate方法一起用于搜索数据库,我们在服务中创建了自定义方法来获取数据,对其进行加密,然后在查询的where子句中使用加密数据。使用User.withCriteria

    时很容易

    class User {

    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
    }
    

    }

答案 4 :(得分:1)

另一种选择是使用JDBC驱动程序,即双向加密/解密数据。请记住,任何解决方案可能都会使加密字段的搜索失效。

恕我直言,最好的解决方案是 longneck 提出的解决方案,从管理到开发,它将使一切变得更加容易。此外,请记住,任何具有客户端加密的解决方案都会使您的所有数据库数据在客户端之外无法使用,也就是说,您将无法使用jdbc客户端或MySQL查询浏览器等不错的工具。

答案 5 :(得分:1)

Jasypt与Hibernate集成:http://jasypt.org/hibernate3.html。但是,不能使用使用WHERE子句的查询

答案 6 :(得分:0)

生成的ids,版本,映射的外键 - 基本上是由Hibernate维护的所有东西 - 除非你打算为你的所有类声明自定义CRUD并在查询中手动加密它们。

对于其他一切你有几个选择:

  1. @PostLoad@PrePersist entity listeners将负责所有非查询操作。
  2. 实现自定义String / Long / Integer / etc ...类型来处理加密将同时处理查询和CRUD操作;但是映射将变得相当混乱。
  3. 您可以在JDBC驱动程序(以及Connection / Statement / PreparedStatement / ResultSet / etc ...)周围编写一个瘦包装器来为您进行加密。
  4. 对于查询,您必须手动处理加密(除非您使用上面的#2),但您应该能够通过单个入口点执行此操作。我不确定Grails如何(或者如果)处理这个问题,但是使用Spring,就像扩展HibernateTemplate一样容易。