# 使用crypto进行加密解密

# 介绍

crypto 是 Nodejs 的内置模块,提供了加密功能,包括对 OpenSSL 的哈希、HMAC、加密、解密、签名、以及验证功能的一整套封装。

# Hash(哈希函数)

又称为不可逆加密函数,主要用于生成数据的一个固定长度的“指纹”,无法通过加密内容解密成原始内容。

安全性排序:SHA3 > SHA256 > SHA1 > MD5

性能排序:MD5 > SHA1 > SHA256 > SHA3

# MD5算法

MD5生成一个128位的哈希值,通常表示为32个十六进制数,在其早期广泛用于各种验证和安全性较低的场合,如检查文件完整性。

const crypto = require('crypto');
let text = 'Hello, world!';
let hash = crypto.createHash('md5').update(text).digest('hex');
console.log('md5 Hash: ', hash);
1
2
3
4

# SHA1算法

SHA-1比MD5安全的哈希算法,生成一个160位的哈希值,通常表示为40个十六进制数。

const crypto = require('crypto');
let text = 'Hello, world!';
let hash = crypto.createHash('sha1').update(text).digest('hex');
console.log('sha1 Hash: ', hash);
1
2
3
4

# SHA256算法

比SHA1更安全的哈希算法,SHA-256生成的哈希值长度为256位,通常以64个十六进制数字表示。这种哈希算法被广泛认为是安全的,适用于多种需要数据完整性和安全性的场合。

const crypto = require('crypto');
// 待哈希的数据
let text = 'Hello, world!';
let hash = crypto.createHash('sha256').update(text).digest('hex');
console.log('SHA-256 Hash: ', hash);
1
2
3
4
5

# SHA3算法

SHA-3(Secure Hash Algorithm 3)是NIST(美国国家标准与技术研究院)在2015年公布的一种加密哈希函数。它是继SHA-1和SHA-2之后,作为新的安全哈希标准而设计的。SHA-3的设计目的是为了提高安全性,尤其是在面对量子计算威胁的背景下。

const crypto = require('crypto');
// 待哈希的数据
let text = 'Hello, world!';
let hash = crypto.createHash('sha3-512').update(text).digest('hex');
console.log('sha3-512 Hash: ', hash);
1
2
3
4
5

# Hmac(加密哈希函数)

Hmac算法也是一种哈希算法,它也可以指定MD5、SHA1、SHA256、SHA3等哈希算法,但需要配置密钥,它主要用于消息认证,即验证一条消息是否未被篡改,并且确实是由持有共享密钥的发送者发送的。

const crypto = require('crypto');
let text = 'Hello, world!';
let hmac = crypto.createHmac('sha256', 'secret-key').update(text).digest('hex');
console.log('SHA-256 Hmac: ', hmac);
1
2
3
4

# AES加解密(对称加密)

AES属于对称加密方法,高级加密标准(Advanced Encryption Standard,AES)

即加密和解密都用同一个密钥

# aes-256-ecb算法

const crypto = require('crypto');

// 密钥
const key = "5d44a032652974c3e53644945a95b126"; // AES-256位密钥

// 待加密的文本
const text = '我是待加密的信息';
console.log('加密前的原文:', text);

// 加密
const cipher = crypto.createCipheriv('aes-256-ecb', key, '');
let encrypted = cipher.update(text, 'utf8', 'base64');
encrypted += cipher.final('base64');
console.log('加密后的密文:', encrypted);

// 解密
const decipher = crypto.createDecipheriv('aes-256-ecb', key, '');
let decrypted = decipher.update(encrypted, 'base64', 'utf8');
decrypted += decipher.final('utf8');
console.log('解密后的明文:', decrypted);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

# aes-256-cbc算法(带偏移量)

const crypto = require('crypto');

// 密钥和偏移量(IV)
const key = "5d44a032652974c3e53644945a95b126"; // AES-256位密钥,必须32位
const iv = "652974c3e5364494"; // 初始化向量(IV)必须16位

// 待加密的文本
const text = '我是待加密的信息';
console.log('加密前的原文:', text);

// 加密
const cipher = crypto.createCipheriv('aes-256-cbc', key, iv);
let encrypted = cipher.update(text, 'utf8', 'base64');
encrypted += cipher.final('base64');
console.log('加密后的密文:', encrypted);

// 解密
const decipher = crypto.createDecipheriv('aes-256-cbc', key, iv);
let decrypted = decipher.update(encrypted, 'base64', 'utf8');
decrypted += decipher.final('utf8');
console.log('解密后的明文:', decrypted);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

# RSA算法(非对称加密)

# RSA加解密

RSA是1977年由罗纳德·李维斯特(Ron Rivest)、阿迪·萨莫尔(Adi Shamir)和伦纳德·阿德曼(Leonard Adleman)一起提出的。当时他们三人都在麻省理工学院工作。RSA就是他们三人姓氏开头字母拼在一起组成的

RSA算法是一种非对称加密算法,与对称加密算法不同的是,RSA算法有两个不同的密钥,一个是公钥,一个是私钥。其中公钥用来加密,私钥用来解密

const crypto = require('crypto');

// 生成PKCS#8格式的RSA密钥对
const { publicKey, privateKey } = crypto.generateKeyPairSync('rsa', {
	modulusLength: 2048, // 密钥长度
	publicKeyEncoding: {
		type: 'spki', // 公钥格式
		format: 'pem', // PEM格式
	},
	privateKeyEncoding: {
		type: 'pkcs8', // 私钥格式
		format: 'pem', // PEM格式
	},
});

console.log('Public Key (PKCS8):', publicKey);
console.log('Private Key (PKCS8):', privateKey);

// 使用公钥加密
const text = '我是待加密的信息';
console.log('加密前的原文:', text);

const encryptBuffer = crypto.publicEncrypt({
		key: publicKey,
		padding: crypto.constants.RSA_PKCS1_OAEP_PADDING, // 填充方式
	},
	Buffer.from(text)
);

console.log('加密后的密文:', encryptBuffer.toString('hex'));

// 使用私钥解密
const decryptBuffer = crypto.privateDecrypt({
		key: privateKey,
		padding: crypto.constants.RSA_PKCS1_OAEP_PADDING,
	},
	encryptBuffer
);

console.log('解密后的明文:', decryptBuffer.toString());
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40

# RSA签名

一般用于对传输的http文本内容进行签名,防止伪造。

与AES加密的区别是,签名是固定长度的字符串,无法通过签名解密原始文本,只能通过原始文本和密钥验证签名是否正确。

RSA 属于非对称加密,即 公钥签名 需要用 私钥验签,而 私钥签名 需要用 公钥验签

// 引入crypto模块
const crypto = require('crypto');

// 要加密的文本
let text = "aaa";
  
// 公钥内容
let publicKeyContent = `MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAi8dG4enrkY+bJJFk4wRVbCv4Mu32INrpWDtX59RA/OEBrxiRBeW+CHCk9mHQsyR76hoTDzOfhDP6QbX2a43ict2XvKlaru87aeSR0zO7JydAWMq1kJJ036aikyflsLPnoIAT3VMDJDrIbJ8C5llbb3zHOHUVtvzKxUZyLBZJhGDzlDA5TAyibrSTXw2bp3pNQ66zEJpKp5qBX8TRueWssQB/LZ/hQ/yCgcqW+paYDXsBdzYt1jNhrKnjIpiLjdXIJT69ZWZ/79Y2bhSLb4lPmV7xAGsiJbpk3OwNHtZFr5Yxut6iY0yul1roZsqX9OQCEIAN6woEup7r4eGeEdlB1QIDAQAB`;
// 私钥内容
let privateKeyContent = `MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCLx0bh6euRj5skkWTjBFVsK/gy7fYg2ulYO1fn1ED84QGvGJEF5b4IcKT2YdCzJHvqGhMPM5+EM/pBtfZrjeJy3Ze8qVqu7ztp5JHTM7snJ0BYyrWQknTfpqKTJ+Wws+eggBPdUwMkOshsnwLmWVtvfMc4dRW2/MrFRnIsFkmEYPOUMDlMDKJutJNfDZunek1DrrMQmkqnmoFfxNG55ayxAH8tn+FD/IKBypb6lpgNewF3Ni3WM2GsqeMimIuN1cglPr1lZn/v1jZuFItviU+ZXvEAayIlumTc7A0e1kWvljG63qJjTK6XWuhmypf05AIQgA3rCgS6nuvh4Z4R2UHVAgMBAAECggEAaULLXUt0C6zyb0pSiCb2UTyXb3sF894HBVvDKiEMQ6MKSpqcc618OwzhHW2x5YYfDr9OBQ+iG8OsvRlTldFGa6v2HawHT193BZqMOllloemMKpGUw2eXPelV2q2b6kLAtnxc+ToPTpQ55Jqma2N1WPLFb/20OZYK+R7A0fSCrn3VUidNGKFFr27Yizj9EM7I/wrSIpo2OLeJ1AOGy6pf4edAKsXP1sQhbzhumAKiUHtai6wzqbxIK3VstqndRnquh1LHlubaXtiBaU6hW8CMNkQD5MAEtJ0UIl5OqxUkMT2OMKmjHy/lFgugiQubzTa2piIFjExlkoDRISRkVzdtAQKBgQDog7y/g0Y/NDJLp/4v8s9NC8T3k5xJSVdtY3yQz8izow08KYNZZmlWRZ2hes4byoLWJ9ff7iH2mj2J49Fq6ZvQ/icWr1aCTl2jWgNpeXpczA/87WBiUqAnK80gnImTnRKz2k7ISG/atC9XW5B9l9QsFfupHwVt53NVvU7pu5z4QQKBgQCZ5Zt/jX6fRWDcEP85nAz4lXgI8gG1uYvVwgYaa31OJIDOvc4pGASydId5p4KXqiCeQv5DVeKG9D8lq1IPR0dT0hLpH6Di6deki12FGxb5HZ0/O1dYXTyWEqrh8Nfj/Gr1puHMmpBzetfVQupHd2o6MyLkoG+e8syfYZ9uXqvElQKBgH3Wlnebx4/7YuEZWXN/2Pvcy8wmImZzgBKezlLdccTvEQGnggQHbikX4jj76sKVtnvK8oWqLs11KqsPFk7jgcX5VxRq7sn1Oa5n0ALskPHaKyj7G7f6+dxZU1o7/iVa1D1sgEjbE1ZtQFXqI2glnNoDR8F/HYQeyIf1vdi4BjtBAoGAfJ52LXKZf0WB6pIE6lSYGE+ItM2rXslSF5UWthwmirl6aG9AWvxtCUjdT0C6ui90XFNpa4NHfPqZi9pQB7kzZAevcoE/GaA8E60a3KcUEkPNyp812oMdhXS2VWFeoOoMfsFVBQaARFLMJZAbACYNqfUwoyvbVz3LPqChppEYzIUCgYAeJ3Y99yLUmSHNyXnUzMM8W/8AVQemCkicUefKFQhlDRkZK4wMtaORJNogok3U1Ym+QsyMN/UxA9w/O4qlDOqsTAR6/2rpS/IJnyXgjRvzGnlcuyIGOxtOFB6IGGh5scsr+pFTJYP212eBhfteS5q9VpE8hx6YL8LKj9QPDbjeGw==`;

// 拼接密钥的函数
const formatKey = function(key, type) {
  return `-----BEGIN ${type}-----\n${key}\n-----END ${type}-----`
};

// 拼接完整的私钥
let privateKey = formatKey(privateKeyContent, "PRIVATE KEY");
// 拼接完整的公钥
let publicKey = formatKey(publicKeyContent, "PUBLIC KEY");

// 生成签名
let sign = crypto.createSign('RSA-SHA256').update(text).sign(privateKey, 'base64');
console.log('sign: ', sign);

// 验证签名是否正确
let verify = crypto.createVerify('RSA-SHA256').update(text).verify(publicKey, sign, 'base64');
console.log('verify: ', verify);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

如何生成密钥对?

公钥和私钥的生成可以使用openssl命令生成,也可以使用nodejs的crypto模块生成

以下是使用nodejs的crypto模块生成密钥对的代码

// 引入crypto模块
const crypto = require('crypto');

// 使用nodejs的crypto模块生成密钥对
const keyPair = crypto.generateKeyPairSync('ec', { 
  namedCurve: 'secp256k1',
  publicKeyEncoding: {
    type: 'spki', 
    format: 'der'
  }, 
  privateKeyEncoding: {
    type: 'pkcs8', 
    format: 'der'
  }
});
let publicKeyContent = keyPair.publicKey.toString('base64');
let privateKeyContent = keyPair.privateKey.toString('base64');

// 拼接密钥的函数
const formatKey = function(key, type) {
  return `-----BEGIN ${type}-----\n${key}\n-----END ${type}-----`
};

// 拼接完整的私钥
let privateKey = formatKey(privateKeyContent, "PRIVATE KEY");
// 拼接完整的公钥
let publicKey = formatKey(publicKeyContent, "PUBLIC KEY");

console.log('privateKey: ', privateKey);
console.log('publicKey: ', publicKey);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

# AES加解密(VK简易版)

注意:需要配置 vk.crypto.aes 用于返回给前端加密数据时的加密密钥

# 在云函数内加解密

// 加密数据
let encryptedKey = vk.crypto.aes.encrypt({
  mode: "aes-256-ecb",
  data: {
    sessionKey: "XXXXX"
  }
});
console.log('encryptedKey: ', encryptedKey)

// 解密 sessionKey 示例
let decryptedRes = vk.crypto.aes.decrypt({
  mode: "aes-256-ecb",
  data: encryptedKey, // 待解密的原文
});
console.log('decryptedRes: ', decryptedRes)
let sessionKey = decryptedRes.sessionKey;
console.log('sessionKey: ', sessionKey)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# 跨云函数双向安全加密通信

A云函数加密请求B云函数

// 加密
let encrypted = vk.crypto.aes.encrypt({
  mode: "aes-256-ecb",
  data: {
    a: 1,
    b: "2"
  }
});
// 请求云函数B
let callFunctionRes = await vk.callFunction({
  url: '云函数B的请求地址',
  data: {
    encrypted, // 只传加密后的数据给云函数B
  }
});
// 解密云函数B返回的数据
let res = vk.crypto.aes.decrypt({
  mode: "aes-256-ecb",
  data: callFunctionRes.encrypted
});
console.log("云函数B返回值: ", res)
return res;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

B云函数解密并执行逻辑

let res = { code: 0, msg: "" };
let {
  encrypted, // 接受A云函数传过来的加密的数据
} = data;
// 解密
let decrypted;
try {
  decrypted = vk.crypto.aes.decrypt({
    mode: "aes-256-ecb",
    data: encrypted
  });
} catch (err) {
  return {
    code: -1,
    msg: "参数未正确加密"
  }
}
// 解密后的数据
console.log("云函数B收到的请求参数: ", decrypted);
let {
  a,
  b
} = decrypted;
// 你的业务逻辑开始-----------------------------------------------------------

res.msg = "调用了B函数";

// 你的业务逻辑结束-----------------------------------------------------------

return {
  // 只返回密文
  encrypted: vk.crypto.aes.encrypt({
    mode: "aes-256-ecb",
    data: res
  })
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36

# 在云函数加密,java或php等其他后端语言解密

以下API需要vk-unicloud核心库版本 >= 2.14.1

# 用云函数加密

// 加密数据
let key = '12345678901234561234567890123456'; // 必须是固定的32位(只支持数字、英文)
let text = { a: 1, b: "2" }; // 待加密的内容

let encrypted = vk.crypto.aes.encrypt({
  mode: "aes-256-ecb",
  data: text,
  key: key,
});
console.log('encrypted: ', encrypted);
1
2
3
4
5
6
7
8
9
10

# 用java解密

import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.util.Base64;

public class CryptoUtil {
    // 调用示例
    public static void main(String[] args) {
        try {
            String encrypted = "es2aF7DWr169X4fvMnlKNg=="; // 待解密的密文
            String key = "12345678901234561234567890123456"; // 必须是固定的32位(只支持数字、英文)
            // 解密
            String decrypted = decrypt(encrypted, key);
            System.out.println("decrypted: " + decrypted);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    // 解密函数
    private static String decrypt(String encryptedData, String key) throws Exception {
        if (key.length() > 32) {
            key = key.substring(0, 32);
        }
        byte[] encryptedBytes = Base64.getDecoder().decode(encryptedData);
        byte[] keyBytes = key.getBytes(StandardCharsets.UTF_8);

        SecretKeySpec secretKey = new SecretKeySpec(keyBytes, "AES");
        Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
        cipher.init(Cipher.DECRYPT_MODE, secretKey);

        byte[] decryptedBytes = cipher.doFinal(encryptedBytes);
        return new String(decryptedBytes, StandardCharsets.UTF_8);
    }
    // 加密函数
    private static String encrypt(String data, String key) throws Exception {
        if (key.length() > 32) {
            key = key.substring(0, 32);
        }
        byte[] dataBytes = data.getBytes(StandardCharsets.UTF_8);
        byte[] keyBytes = key.getBytes(StandardCharsets.UTF_8);

        SecretKeySpec secretKey = new SecretKeySpec(keyBytes, "AES");
        Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
        cipher.init(Cipher.ENCRYPT_MODE, secretKey);

        byte[] encryptedBytes = cipher.doFinal(dataBytes);
        return Base64.getEncoder().encodeToString(encryptedBytes);
    }

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51

# 用php解密

<?php
    $key = '12345678901234561234567890123456'; // 必须是固定的32位(只支持数字、英文)
    $encrypt = "es2aF7DWr169X4fvMnlKNg=="; // 待解密的内容
    // 解密
    $decrypt = openssl_decrypt(base64_decode($encrypt), 'aes-256-ecb', substr($key, 0, 32), OPENSSL_RAW_DATA);
    echo $decrypt;
?>
1
2
3
4
5
6
7

# 在java或php等其他后端语言加密,用云函数解密

# 用云函数解密

// 加密数据
let key = '12345678901234561234567890123456'; // 必须是固定的32位(只支持数字、英文)
let encrypted = "es2aF7DWr169X4fvMnlKNg=="; // 待解密的内容
// 解密
let decrypted = vk.crypto.aes.decrypt({
  mode: "aes-256-ecb",
  data: encrypted, // 待解密的内容
  key: key,
});
console.log('decrypted: ', decrypted);
1
2
3
4
5
6
7
8
9
10

# 用java加密

import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.util.Base64;

public class CryptoUtil {
    // 调用示例
    public static void main(String[] args) {
        try {
            String key = "12345678901234561234567890123456"; // 必须是固定的32位(只支持数字、英文)
            String text = "{\"a\":1,\"b\":\"2\"}"; // 待加密的内容
            // 加密
            String encrypted = encrypt(text, key);
            System.out.println("encrypted: " + encrypted);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    // 解密函数
    private static String decrypt(String encryptedData, String key) throws Exception {
        if (key.length() > 32) {
            key = key.substring(0, 32);
        }
        byte[] encryptedBytes = Base64.getDecoder().decode(encryptedData);
        byte[] keyBytes = key.getBytes(StandardCharsets.UTF_8);

        SecretKeySpec secretKey = new SecretKeySpec(keyBytes, "AES");
        Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
        cipher.init(Cipher.DECRYPT_MODE, secretKey);

        byte[] decryptedBytes = cipher.doFinal(encryptedBytes);
        return new String(decryptedBytes, StandardCharsets.UTF_8);
    }
    // 加密函数
    private static String encrypt(String data, String key) throws Exception {
        if (key.length() > 32) {
            key = key.substring(0, 32);
        }
        byte[] dataBytes = data.getBytes(StandardCharsets.UTF_8);
        byte[] keyBytes = key.getBytes(StandardCharsets.UTF_8);

        SecretKeySpec secretKey = new SecretKeySpec(keyBytes, "AES");
        Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
        cipher.init(Cipher.ENCRYPT_MODE, secretKey);

        byte[] encryptedBytes = cipher.doFinal(dataBytes);
        return Base64.getEncoder().encodeToString(encryptedBytes);
    }

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51

# 用php加密

<?php
    $key = '12345678901234561234567890123456'; // 必须是固定的32位(只支持数字、英文)
    $text = '{"a":1,"b":"2"}'; // 待加密的内容
    // 解密
    $encrypted = base64_encode(openssl_encrypt($text, 'aes-256-ecb', substr($key, 0, 32), OPENSSL_RAW_DATA));
    echo $encrypted;
?>
1
2
3
4
5
6
7