mongoose-encryption, Mongoose的简单加密和认证插件

分享于 

20分钟阅读

GitHub

  繁體 雙語
Simple encryption plugin for Mongoose
  • 源代码名称:mongoose-encryption
  • 源代码网址:http://www.github.com/joegoldbeck/mongoose-encryption
  • mongoose-encryption源代码文档
  • mongoose-encryption源代码下载
  • Git URL:
    git://www.github.com/joegoldbeck/mongoose-encryption.git
    Git Clone代码到本地:
    git clone http://www.github.com/joegoldbeck/mongoose-encryption
    Subversion代码到本地:
    $ svn co --depth empty http://www.github.com/joegoldbeck/mongoose-encryption
    Checked out revision 1.
    $ cd repo
    $ svn up trunk
    
    mongoose加密

    mongoose文档的简单加密和认证。 依赖于 node crypto MODULE。 保存和查找期间透明地进行加密和解密。 插件利用mongoDB文档的BSON特性一次性加密多个字段,而不是单独加密字段。

    工作原理

    使用 AES-256-CBC 对每个操作使用随机的唯一初始化向量进行加密。 使用 HMAC-SHA-512 执行身份验证。

    要加密,将从文档中删除相关字段,转换为 JSON,以 Buffer 格式加密,并插入文档的_ct 字段。 Mongoose将 _ct 字段发送到mongo时将字段转换为 Binary

    若要解密,_ct 字段被解密,解析 JSON,并将各个字段作为原始数据类型插入到文档中。

    为了签名,相关字段( 必须包括 _id_ct ) 被稳定字符串化并与签名字段。集合 NAME 和插件版本签名。 这个签名是以 Buffer 格式存储在 _ac 字段中的插件版本和附加的签名字段列表。 Mongoose将字段发送到mongo时将字段转换为 Binary

    要进行身份验证,签名按照上面的样式生成,并与文档的_ac 字段相比较。 如果签名相等,则验证成功。 如果不是,或者如果文档中缺少 _ac,身份验证将失败并传递给回调的错误。

    save 中,文档被加密然后签名。 在 find 期间,对文档进行身份验证并解密

    在你开始之前,你可以使用

    阅读下面的安全笔记

    安装

    npm install mongoose-encryption

    用法

    单独生成和存储密钥。 他们应该在 环境变量 中,但一定不要失去他们。 可以使用任意长度的单个 secret 字符串,也可以使用一对base64字符串(。32-byte encryptionKey 和 64-byte signingKey )。

    安全地生成这对密钥的一个好方法是 openssl rand -base64 32; openssl rand -base64 64;

    基本

    默认情况下,除 _id__v 和具有索引的字段外,所有字段都是加密

    
    var mongoose = require('mongoose');
    
    
    var encrypt = require('mongoose-encryption');
    
    
    
    var userSchema = new mongoose.Schema({
    
    
     name: String,
    
    
     age: Number
    
    
    //whatever else
    
    
    });
    
    
    
    //Add any other plugins or middleware here. For example, middleware for hashing passwords
    
    
    
    var encKey = process.env.SOME_32BYTE_BASE64_STRING;
    
    
    var sigKey = process.env.SOME_64BYTE_BASE64_STRING;
    
    
    
    userSchema.plugin(encrypt, { encryptionKey: encKey, signingKey: sigKey });
    
    
    //This adds _ct and _ac fields to the schema, as well as pre 'init' and pre 'save' middleware,
    
    
    //and encrypt, decrypt, sign, and authenticate instance methods
    
    
    
    User = mongoose.model('User', userSchema);
    
    
    
    

    所有的文件都可以正常工作。如果你希望文档被验证和解密,那么 find 可以透明地使用 New 文档,但是你不应该使用 lean 选项。 findOnefindById,等等。 ,以及 savecreate 也都正常工作。 update 将在未加密和未经认证的字段上工作,但是在加密或者经过身份验证的字段。

    从加密中排除某些字段

    若要排除其他字段( 除_id和索引字段之外),请传递 excludeFromEncryption 选项

    
    //exclude age from encryption, still encrypt name. _id will also remain unencrypted
    
    
    userSchema.plugin(encrypt, { encryptionKey: encKey, signingKey: sigKey, excludeFromEncryption: ['age'] });
    
    
    
    

    只加密某些字段

    你还可以使用 encryptedFields 选项指定要加密哪些字段。 这将覆盖默认值和所有其他选项。

    
    //encrypt age regardless of any other options. name and _id will be left unencrypted
    
    
    userSchema.plugin(encrypt, { encryptionKey: encKey, signingKey: sigKey, encryptedFields: ['age'] });
    
    
    
    

    验证附加字段

    默认情况下,文档的加密部分与 _id 一起验证,以防止攻击者使用数据库写访问来复制/粘贴攻击。 如果使用上述选项之一,以便仅对文档的部分内容进行加密,则可能需要对保存在明文中的字段进行身份验证以防止篡改。 特别是,考虑验证用于授权的任何字段,例如 emailisAdmin 或者 password ( 尽管密码应该在加密的块中)。 你可以使用 additionalAuthenticatedFields 选项进行这里操作。

    
    //keep isAdmin in clear but pass error on find() if tampered with
    
    
    userSchema.plugin(encrypt, {
    
    
     encryptionKey: encKey,
    
    
     signingKey: sigKey,
    
    
     excludeFromEncryption: ['isAdmin'],
    
    
     additionalAuthenticatedFields: ['isAdmin']
    
    
    });
    
    
    
    

    注意,最安全的选择是包括所有非加密字段进行身份验证,因为这样可以防止篡改文档的任何部分。

    嵌套字段

    可以使用点表示法在选项中处理嵌套字段。 比如, encryptedFields: ['nest.secretBird']

    重命名加密的集合

    为了防止交叉收集攻击,集合 NAME 包含在已经签名的块中。 如果你只更改 Mongo ( 因此在Mongoose中更新模型 NAME ) 中集合的NAME,那么认证将失败。 要恢复功能,请使用旧模型名传入 collectionId 选项。

    
    //used to be the `users` collection, now it's `powerusers`
    
    
    poweruserSchema.plugin(encrypt, {
    
    
     encryptionKey: encKey,
    
    
     signingKey: sigKey,
    
    
     collectionId: `User`//this corresponds to the old model name
    
    
    });
    
    
    
    PowerUser = mongoose.model('PowerUser', poweruserSchema);
    
    
    
    

    加密子文档的特定字段

    你甚至可以加密子文档的字段,你只需要将 encrypt 插件添加到子文档模式。 如果你还没有将 encrypt 插件添加到父模式中,那么你应该考虑将插件添加到父模式中,如果你继续使用验证错误所导致的文档,那么它将保存到父模式。

    
    var hidingPlaceSchema = new Schema({
    
    
     latitude: Number,
    
    
     longitude: Number,
    
    
     nickname: String
    
    
    });
    
    
    
    hidingPlaceSchema.plugin(encrypt, {
    
    
     encryptionKey: encKey,
    
    
     signingKey: sigKey,
    
    
     excludeFromEncryption: ['nickname']
    
    
    });
    
    
    
    var userSchema = new Schema({
    
    
     name: String,
    
    
     locationsOfGold: [hidingPlaceSchema]
    
    
    });
    
    
    
    //optional but recommended: authenticate subdocuments from the parent document
    
    
    userSchema.plugin(encrypt, {
    
    
     encryptionKey: encKey,
    
    
     signingKey: sigKey,
    
    
     additionalAuthenticatedFields: ['locationsOfGold'],
    
    
     encryptedFields: []
    
    
    });
    
    
    
    //optional in Mongoose 3.x, not necessary in Mongoose 4.x. only needed for correct document behavior following validation errors during a save
    
    
    userSchema.plugin(encrypt.encryptedChildren);
    
    
    
    
    

    Mongoose 3.x 中的encrypt.encryptedChildren 需要,因为在这些Mongoose版本中,子文档的'预保存'挂钩在父验证失败时被调用。 如果在失败保存后修复父文档,然后尝试保存,子文档中的数据将丢失。 在 Mongoose 4. x, 中,这里行为是固定的。

    保存行为

    默认情况下,文档在保存到数据库后被解密,这样你就可以继续透明地使用它们。

    
    joe = new User ({ name: 'Joe', age: 42 });
    
    
    joe.save(function(err){//encrypted when sent to the database
    
    
    //decrypted in the callback
    
    
     console.log(joe.name);//Joe
    
    
     console.log(joe.age);//42
    
    
     console.log(joe._ct);//undefined
    
    
    });
    
    
    
    
    

    你可以使用 decryptPostSave 选项来关闭这里行为,并稍微提高性能。

    
    userSchema.plugin(encrypt, {.. ., decryptPostSave: false });
    
    
    ...
    
    
    joe = new User ({ name: 'Joe', age: 42 });
    
    
    joe.save(function(err){
    
    
     console.log(joe.name);//undefined
    
    
     console.log(joe.age);//undefined
    
    
     console.log(joe._ct);//<Buffer 61 41 55 62 33.. .
    
    
    });
    
    
    
    

    的秘密字符串而不是两个键

    为了方便,你还可以传递单个秘密字符串,而不是两个键。

    
    var secret = process.env.SOME_LONG_UNGUESSABLE_STRING;
    
    
    userSchema.plugin(encrypt, { secret: secret });
    
    
    
    

    更改选项

    在大多数情况下,你可以更新插件选项。 这将不会立即更改存储在数据库中的内容,但是它将改变文档保存的方式。

    但是,一旦你开始将下列选项用于集合,则不能更改这些选项:

    • secret
    • encryptionKey
    • signingKey
    • collectionId

    实例方法

    你还可以在( 只要模型包含插件) 中加密。解密。签名和验证文档。 decryptsignauthenticate 都是幂等的。 encrypt 不是。

    
    joe = new User ({ name: 'Joe', age: 42 });
    
    
    joe.encrypt(function(err){
    
    
     if (err) { return handleError(err); }
    
    
     console.log(joe.name);//undefined
    
    
     console.log(joe.age);//undefined
    
    
     console.log(joe._ct);//<Buffer 61 41 55 62 33.. .
    
    
    
     joe.decrypt(function(err){
    
    
     if (err) { return handleError(err); }
    
    
     console.log(joe.name);//Joe
    
    
     console.log(joe.age);//42
    
    
     console.log(joe._ct);//undefined
    
    
     });
    
    
    });
    
    
    
    
    
    
    joe.age = 30
    
    
    
    joe.sign(function(err){
    
    
     if (err) { return handleError(err); }
    
    
     console.log(joe.name);//Joe
    
    
     console.log(joe.age);//30
    
    
     console.log(joe._ac);//<Buffer 61 fa 63 95 50
    
    
    
     joe.authenticate(function(err){
    
    
     if (err) { return handleError(err); }
    
    
     console.log(joe.name);//Joe
    
    
     console.log(joe.age);//30
    
    
     console.log(joe._ac);//<Buffer 61 fa 63 95 50
    
    
    
     joe.age = 22
    
    
    
     joe.authenticate(function(err){//authenticate without signing changes, error is passed to callback
    
    
     if (err) { return handleError(err); }//this conditional is executed
    
    
     console.log(joe.name);//this won't execute
    
    
     });
    
    
    });
    
    
    
    

    还有 decryptSyncauthenticateSync 函数,它们同步执行,如果出现错误则抛出。

    已经开始使用现有的集合

    如果在空集合上使用mongoose加密,则可以立即开始使用它。 若要在现有集合中使用它,你需要运行迁移或者使用较低安全选项。

    安全方式

    为了防止篡改文档,默认情况下,每个文档在 find 上都需要签名。 类方法 migrateToA() 对集合中的所有文档进行加密和签名。 这应该不用说,但是在运行下面的迁移之前,对数据库进行的备份。

    
    //This should be run in a separate migration script
    
    
    userSchema.plugin(encrypt.migrations, {.. .. });
    
    
    User = mongoose.model('User', userSchema);
    
    
    User.migrateToA(function(err){
    
    
     if (err){ throw err; }
    
    
     console.log('Migration successful');
    
    
    });
    
    
    
    

    在迁移之后,你可以像上面一样使用插件。

    快速方式

    你还可以在没有迁移的情况下在现有集合上使用插件,通过允许身份验证成功地在文档。 这是不安全的,但你随后可以通过 switch 更安全的选项。

    
    userSchema.plugin(encrypt, { requireAuthenticationCode: false,.. .. });
    
    
    
    

    从版本 ≤ 0.11.0迁移

    如果你使用的是早期版本的mongoose加密,建议你升级。 这个版本添加了身份验证,而且不需要写入你的数据库的攻击者可以解密文档。

    • 解决中断更改

      • 重命名 key -> encryptionKey
      • signingKey 添加为 64-byte base64字符串( 生成 openssl rand -base64 64 )
      • 运行迁移
        • 如果已经加密子文档,请首先在父集合上运行类方法 migrateSubDocsToA()

          
          //Only if there are encrypted subdocuments
          
          
          //Prepends plugin version to _ct
          
          
          userSchema.plugin(encrypt.migrations, {.. .. });
          
          
          User = mongoose.model('User', userSchema);
          
          
          User.migrateSubDocsToA('locationsOfGold', function(err){
          
          
           if (err){ throw err; }
          
          
           console.log('Subdocument migration successful');
          
          
          });
          
          
          
          
        • 在任何加密集合( 不是子文档) 上运行类方法 migrateToA()

          
          //Prepends plugin version to _ct and signs all documents
          
          
          userSchema.plugin(encrypt.migrations, {.. .. });
          
          
          User = mongoose.model('User', userSchema);
          
          
          User.migrateToA(function(err){
          
          
           if (err){ throw err; }
          
          
           console.log('Migration successful');
          
          
          });
          
          
          
          
    • 建议

      • additionalAuthenticatedFields 设置为至少包括授权访问应用程序中的文档所涉及的所有字段
      • 如果使用加密子文档,请注意这里的其他建议
    • Deprecations

      • 重命名 fields -> encryptedFields
      • 重命名 exclude -> excludeFromEncryption

    的优点:一次加密多个域的优点

    优点:

    • 所有通过单个代码路径支持的Mongoose数据类型
    • 使用整个文档时的加密/解密速度更快
    • 更小的加密文档

    缺点:

    • 无法在查询中选择单个加密字段,也不能通过更新操作取消或者重命名加密字段
    • 可能在你只想解密文档子集的情况下可能会变慢
    • 包括整个加密/验证块的事务有效实施。 更新任何加密或者认证的字段强制它们都被标记为已经修改。

    安全注意事项

    • 始终将密钥和密钥存储在版本控制之外,并与数据库分离。 应用程序服务器上的环境变量适用于这里。
    • 另外,将你的加密密钥存储在。 如果遗失了,就无法取回加密数据。
    • 加密密码不能替代适当的散列。 bcrypt 是一个不错的选择。 这里有一个好的实现工具。 对密码进行散列处理后,也可以对它的进行加密。 深度防御,就像他们说的。 只要将mongoose加密插件添加到任何散列中间件的模式中即可。
    • 如果攻击者获得了对应用程序服务器的访问权限,他们可能会访问数据库和密钥。 此时,加密和身份验证都不适合你。

    如何运行单元测试

    • 如果你还没有安装它,使用 npm install安装依赖项
    • mongod 启动 mongo
    • 使用 npm test 运行测试

    安全问题报告/免责声明

    没有一个作者是安全专家。 我们依靠受接受的工具和实践,尝试使这个工具实体和良好测试,但完美无缺。 在使用( 请注意下面的法律免责声明) 之前,请仔细查看代码。 你发现或者怀疑任何安全相关问题,请在 security@cinchfinancial.com,我们将马上向你发送电子邮件。 对于non-security-related问题,请打开Github问题或者请求请求。

    确认

    极大地感谢 Financial Financial通过版本 1.0支持这个插件,以及@stash 插件,指出缺少认证和为版本 0.12.0提供宝贵的指南和评论。

    就像用各种不同的工具? Eth: 0 xb53b70d5BE66a03E85F6502d1D060871a79a47f7

    许可证

    麻省理工学院许可证( MIT )

    版权所有( c ) 2016,2017 Joseph Goldbeck

    版权所有( c ) 2014 -2015约瑟夫Goldbeck和连接财务,有限责任公司

    若要在取得该软件副本时免费授予任何人,如有下列条件的软件,请免费授予该软件的副本,并与相关的文档文件("软件") 进行许可,包括不受限制的权利,包括以下条件:

    上述版权声明和本许可声明须包括在所有的副本或实质性部分的软件。

    软件是"是",没有任何保证,表示或者隐含,包括但不限于销售,适合特定用途和 NONINFRINGEMENT。 作者或者版权持有人在合同。侵权或者它的他与软件或者它的他用户交易的行为。


    plugin  auth  mongo  encrypt  加密  Mongoose  
    相关文章