likes
comments
collection
share

NodeJS AES加解密简明实现和分析

作者站长头像
站长
· 阅读数 48

Nodejs内置了强大的crypto密码学套件,可以非常方便简明的实现各种密码学计算和操作。本文探讨了一套基于crypto的对称信息加密的实现和过程。

需求和构想

这是一个基本的对称加密实现,但是在基本的AES加密技术的基础上,我们希望提供更好的安全性,所以我们的技术实现构想如下:

  • 基于AES,密钥长度256,支持认证标签技术authTag(如CCM或GCM模式)
  • 一次一密,加密结果动态
  • 加密结果为单一的Base64
  • 可以进行时效性检查
  • 实现过程尽可能清楚、简单、高效

设置和参数

作为示例,我们选择的加密算法和设置包括(封装为一个常量,用于代码中的实现):

const AES = {
    Aprefix : "U",
    Mode: "aes-256-gcm",
    Keylength: 32,
    Taglength: 8,
    Hash: "SHA256"
};

这些设置包括:

  • 模式: AES-256-GCM, AES有很多模式,其中GCM是比较先进、安全和完整的,如支持初始化向量和认证标签,可以提供更好的安全性和完整性检查
  • authTagLength:8bit, GCM模式要求有附加的信息,来保证加解密过程中信息的完整性
  • keyLength: 32,密钥长度
  • 密钥衍生算法基础为SHA256

加密

// require cryptoe
const {
    randomBytes,
    createHash, createHmac,
    createCipheriv, createDecipheriv, hkdfSync
} = require("crypto");

// encrypt content string with a key buffer
const encrypt = (str, fkey) =>{
    // prepare the params 
    let 
    // time in salt 
    btime = Buffer.from((0 | Date.now()/1000).toString(16),"hex"),
    // add into salt 
    salt  = Buffer.concat([randomBytes(AES.Taglength - btime.byteLength), btime ]),
    // compute a int for iv 
    iter  = salt.reduce((c,v)=>(c + v) % 256),  // 512 ~ 1024
    iv    = salt.map(v=>v^(iter % 256)),
    // deveris the key 
    key   = hkdfSync(AES.Hash, fkey.slice(0,20), salt, fkey.slice(-20), AES.Keylength), 
    // content buffer 
    bufContent = Buffer.from(str);

    // preare the ciphter 
    let cipher = createCipheriv( AES.Mode, key, iv, { authTagLength: AES.Taglength});

    // concat update final auth salt 
    let rstr = Buffer.concat([
        cipher.update(bufContent),
        cipher.final(),
        cipher.getAuthTag(),
        salt 
    ]).toString("base64");

    // add prefix if needed
    if ( AES.Aprefix) rstr = AES.Aprefix + rstr;

    return  rstr;
}

要点说明:

  • 我们选择的加密算法为 aes-256-gcm (在浏览器环境中也可以支持)
  • 混合时间和随机信息,生成加密用的盐
  • 使用密钥派生函数,基于原始密钥和盐每次都可以生成新的加密密钥
  • 使用盐生成初始向量
  • 使用标准cipheriv方法,对明文进行加密
  • 简单的使用Buffer连接加密内容、Tag和IV生成最后的加密结果,并输出为base64格式

解密

解密的函数如下,输入参数为加密后的base64字符串和密钥buffer:


// decrypt a encrypte base64 string from encrypt function with same key buffer
const decrypt = (str64, fkey)=> {
    let 
    // content buf 
    buf = Buffer.from(AES.Aprefix ? str64.slice(AES.Aprefix.length): str64,"base64");
    // salt segment 
    salt  = buf.slice(-AES.Taglength),
    // compute iv from salt  
    iter  = salt.reduce((c,v)=>(c + v) % 256),  // 512 ~ 1024
    iv    = salt.map(v=>v^(iter % 256)),
    // buf tag 
    authTag    = buf.slice(-2 * AES.Taglength, -AES.Taglength),
    // buf content
    bufContent = buf.slice(0, -2 * AES.Taglength),
    // deveris the key 
    key   = hkdfSync(AES.Hash, fkey.slice(0,20), salt, fkey.slice(-20), AES.Keylength); 

    // check time 
    let ctime = parseInt(salt.slice(-4).toString("hex"),16);
    if (ctime + 600 < ( 0 | Date.now() / 1000)) { // 10 minites
        console.log("DecryptContentTimeOut");
        return null;
    };

    let rstr = null;
    try {
        // constuct deciphter
        let decipher = createDecipheriv( AES.Mode, key, iv, { authTagLength: AES.Taglength});
        decipher.setAuthTag(authTag);
    
        rstr = Buffer.concat([
            decipher.update(bufContent),
            decipher.final()
        ]).toString();
    } catch (error) {
        console.log(error);
    }
    
    return rstr; 
}

要点说明:

  • 解密内容其实是一个预定格式的结合了密文、Tag和IV的综合体,可以转为Buffer方便处理
  • 将Buffer按照格式分解
  • key的计算方式同加密端
  • 解密方式基本上就是加密的逆流程
  • 解密的结果是一个buffer,也可以方便的解码为UTF8字符串或者JSON

调用方式

在上述函数实现的基础上,调用其进行加解密的过程如下:

    let passwd = "一个机密";
    let ptext = "China中国";
    let key = createHash(AES.Hash).update(passwd).digest();

    let r = encrypt(ptext, key);

    console.log("Encrypt:", r);
    
    let d = decrypt(r, key);
    console.log("Decrypt:", d);

特点和优势

  • 使用aes-256-gcm这一比较先进和安全的模式
  • 直接将iv和tag加入到加密结果中,简单紧凑
  • 可选的解密有效时限和检查
  • 可选加密头标识算法和参数选项,提高扩展性和兼容性
  • 真实的密钥基于随机盐衍生,一次一密更安全
  • 使用Buffer进行加密和解密操作,简单高效
  • 每次加密的结果都不同,增加了破解的难度
  • 我们这里为了简单和方便说明,使用hmac对密码做了一个简单的变换。在生产和实际环境中,应当使用诸如hkdf或者pbkdf2等密钥衍生算法来从密码来生成密钥,更为安全

扩展

在深入的理解了这套对称加解密实现的基础上,我们可以轻易的对其进行扩展,来提高安全性,或者满足不同的业务场景。

  • 密钥的派生其实和密码的形式和格式无关,也就是说是可以支持中文的
  • 显然密钥也可以来自密钥协商的结果,如DH,从而和非对称加密很好的结合
  • 使用时间检查,可以在一定程度上对抗重放攻击
  • 加解密内容其实是不限的,因此可以直接加密和解密JSON对象
转载自:https://juejin.cn/post/7225525540249174074
评论
请登录