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

Mysql 数据库的Hibernate加密对应用程序完全透明,mysql,database,hibernate,encryption,grails,Mysql,Database,Hibernate,Encryption,Grails,我正在从事一个Grails1.0.4项目,该项目必须在不到两周的时间内发布,而客户刚刚提出了一个要求,即数据库中的所有数据都应该加密 由于对应用程序本身中的每个数据库访问进行加密可能需要大量时间,并且容易出错,因此我寻求的解决方案是某种对应用程序透明的加密 有没有办法设置Hibernate来加密所有表中的所有数据(除了id和version列),或者我应该寻找一个MySQL解决方案(我们使用的是MySQL 5.0) 编辑: 感谢您所有关于替代解决方案的帖子,如果客户改变主意,那就太好了。就目前而言

我正在从事一个Grails1.0.4项目,该项目必须在不到两周的时间内发布,而客户刚刚提出了一个要求,即数据库中的所有数据都应该加密

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

有没有办法设置Hibernate来加密所有表中的所有数据(除了id和version列),或者我应该寻找一个MySQL解决方案(我们使用的是MySQL 5.0)

编辑: 感谢您所有关于替代解决方案的帖子,如果客户改变主意,那就太好了。就目前而言,要求是“数据库中没有纯文本”


我想指出的第二件事是,我使用的是Grails,对于那些不熟悉Grails的人来说,这是一种约定而非配置,所以即使是不按约定对应用程序进行的小更改也应该避免,映射外键(基本上是由Hibernate维护的所有外键)将被删除,除非您打算为所有类声明自定义CRUD并在查询中手动加密它们

除此之外,您还有两种选择:

  • @PostLoad
    @PrePersist
    将处理所有非查询操作
  • 正在实现自定义字符串/长/整数/等。。。处理加密的类型将同时处理查询和CRUD操作;然而,映射将变得相当混乱
  • 您可以围绕JDBC驱动程序(以及Connection/Statement/PreparedStatement/ResultSet/etc…)编写一个瘦包装器来为您进行加密

  • 就查询而言,您必须手动处理加密(除非您使用上面的#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
      }
      
      }


    请确保向客户指出这将对应用程序中所有内容的性能产生的影响。我们只加密我们法律上必须加密的数据,如SSN和tax id,当我们需要插入或读取这些字段时,它仍然会减慢速度。对每个表中的每个字段都这样做将对性能产生巨大的影响。如果他们想受到打击,这没关系,但让他们知道这可能会让网站的速度慢到用户无法接受的程度。在你这么做之前,让他们现在就知道,并让他们以书面形式签字,以保护你的公司。在不到两周的时间内完成这项工作并进行全面测试,真是幸运。这确实是一个长达数月的项目,因为它彻底改变了应用程序的所有方面。这听起来真是个糟糕的主意。当它发生变化时,考虑所有的影响
    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
    }