crypto加密参数

  |  

HMac 算法

  1. sha256 加密 微信参数加密
1
2
3
const hash = crypto.createHmac('sha256', 秘钥).update('需要加密的参数 string类型的参数').digest('hex');

return hash.toUpperCase();

签名和验证算法

  1. RSA 秘钥加密 转 base64 支付宝参数加密
1
2
3
const sign = crypto.createSign('RSA-SHA1');
sign.update(querystring);
sign.sign(this.privateKey, 'base64');
  1. RSA2 秘钥加密 转 base64 支付宝参数加密
1
2
3
const sign = crypto.createSign('RSA-SHA256');
sign.update(querystring);
sign.sign(this.privateKey, 'base64');
  1. 公钥加密 支付宝 回调 sign 验证
1
2
3
4
5
6
7
8
9
10
let verify;
if (sign_type.toUpperCase() === 'RSA2') {
verify = crypto.createVerify('RSA-SHA256');
} else if (sign_type.toUpperCase() === 'RSA') {
verify = crypto.createVerify('RSA-SHA1');
} else {
throw new Error('请传入正确的签名方式,sign_type:' + sign_type);
}
verify.update(querystring);
return verify.verify(this.publicKey, sign, 'base64');

HMac 算法 md5

  1. 数据内容签名:把(请求内容(未编码)+AppKey)进行 MD5 加密,然后 Base64 编码,最后 进行 URL(utf-8)编码。快递鸟签名
1
2
3
4
5
6
7
const hash = crypto.createHash('md5');
hash.update(requestData);
hash.update(key);
let sign = hash.digest('hex');
sign = Buffer.from(sign).toString('base64');
sign = encodeURIComponent(sign);
return sign;

非对称加密解密 (RSA)

借助 openssl 生成私钥和公钥:

1
2
3
4
# 生成私钥
openssl genrsa -out privatekey.pem 1024
# 生成公钥
openssl rsa -in privatekey.pem -pubout -out publickey.pem

对 hello world! 加密和解密的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const crypto = require("crypto");
const fs = require("fs");

const privateKey = fs.readFileSync("./privatekey.pem");
const publicKey = fs.readFileSync("./publickey.pem");

const content = "hello world!"; // 待加密的明文内容

// 公钥加密
const encodeData = crypto.publicEncrypt(publicKey, Buffer.from(content));
console.log(encodeData.toString("base64"));
// 私钥解密
const decodeData = crypto.privateDecrypt(privateKey, encodeData);
console.log(decodeData.toString("utf8"));

对称加密 (AES)

下面是用 aes-256-cbc 算法对明文进行加密:

1
2
3
4
5
6
7
8
9
10
11
12
13
const crypto = require("crypto");

const secret = crypto.randomBytes(32); // 密钥
const content = "hello world!"; // 要加密的明文

const cipher = crypto.createCipheriv(
"aes-256-cbc",
secret,
Buffer.alloc(16, 0)
);
cipher.update(content, "utf8");
// 加密后的结果:e2a927165757acc609a89c093d8e3af5
console.log(cipher.final("hex"));

注意:在使用加密算法的时候,给定的密钥长度是有要求的,否则会爆出 this[kHandle].initiv(cipher, credential, iv, authTagLength); Error: Invalid key length… 的错误。以 aes-256-cbc 算法为例,需要 256 bits = 32 bytes 大小的密钥。同样地,AES 的 IV 也是有要求的,需要 128bits。(请参考 “参考链接” 部分)

使用 32 个连续 I 作为密钥,用 aes-256-cbc 加密后的结果是 a061e67f5643d948418fdb150745f24d。下面是逆向解密的过程:

1
2
3
4
5
6
7
8
const secret = "I".repeat(32);
const decipher = crypto.createDecipheriv(
"aes-256-cbc",
secret,
Buffer.alloc(16, 0)
);
decipher.update("a061e67f5643d948418fdb150745f24d", "hex");
console.log(decipher.final("utf8")); // 解密后的结果:hello world!

图片文件的文件摘要

即对图片文件的二进制内容进行 sha256 计算得到的值

1
2
3
4
const pic_buffer = fs.readFileSync('72fe0092be0cf9dd8420579cc954fb4e.jpg');
const sign = crypto.createHash('sha256');
sign.update(pic_buffer);
console.log(sign.digest('hex'))

公钥加密(crypto.constants.RSA_PKCS1_OAEP_PADDING)

文档链接:微信支付-敏感信息加解密

对应 java 签名方法是 Cipher.getinstance(RSA/ECB/OAEPWithSHA-1AndMGF1Padding)

1
2
3
4
5
6
7
8
9
10
11
12
/**
* 敏感信息加密
* @param str 敏感信息字段(如用户的住址、银行卡号、手机号码等)
* @returns
*/
public publicEncrypt(str: string, padding = crypto.constants.RSA_PKCS1_OAEP_PADDING) {
if (![crypto.constants.RSA_PKCS1_PADDING, crypto.constants.RSA_PKCS1_OAEP_PADDING].includes(padding)) {
throw new Error(`Doesn't supported the padding mode(${padding}), here's only support RSA_PKCS1_OAEP_PADDING or RSA_PKCS1_PADDING.`);
}
const encrypted = crypto.publicEncrypt({ key: this.publicKey, padding, oaepHash: 'sha1' }, Buffer.from(str, 'utf8')).toString('base64');
return encrypted;
}

私钥解密(crypto.constants.RSA_PKCS1_OAEP_PADDING)

文档链接:微信支付-敏感信息加解密

对应 java 签名方法是 Cipher.getinstance(RSA/ECB/OAEPWithSHA-1AndMGF1Padding)

1
2
3
4
5
6
7
8
9
10
11
12
/**
* 敏感信息解密
* @param str 敏感信息字段(如用户的住址、银行卡号、手机号码等)
* @returns
*/
public privateDecrypt(str: string, padding = crypto.constants.RSA_PKCS1_OAEP_PADDING) {
if (![crypto.constants.RSA_PKCS1_PADDING, crypto.constants.RSA_PKCS1_OAEP_PADDING].includes(padding)) {
throw new Error(`Doesn't supported the padding mode(${padding}), here's only support RSA_PKCS1_OAEP_PADDING or RSA_PKCS1_PADDING.`);
}
const decrypted = crypto.privateDecrypt({ key: this.privateKey as Buffer, padding, oaepHash: 'sha1' }, Buffer.from(str, 'base64'));
return decrypted.toString('utf8');
}

使用对称加密算法 AES/ECB/PKCS5Padding,对请求体和响应体进行加密与解密,结果采用 Base64 编码。

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
// node

/**
* AES/ECB/PKCS5Padding 加密
*/
public aesEncrypt(data: string) {
const cipherChunks = [];
const cipher = crypto.createCipheriv('aes-128-ecb', Buffer.from(this.AES_KEY, 'base64'), Buffer.alloc(0));
cipher.setAutoPadding(true);

cipherChunks.push(cipher.update(data, 'utf8', 'base64'));
cipherChunks.push(cipher.final('base64'));

return cipherChunks.join('');
}
/**
* AES/ECB/PKCS5Padding 解密
*/
public aesDecrypt(data: string) {
const cipherChunks = [];
const decipher = crypto.createDecipheriv('aes-128-ecb', Buffer.from(this.AES_KEY, 'base64'), Buffer.alloc(0));
decipher.setAutoPadding(true);

cipherChunks.push(decipher.update(data, 'base64', 'utf8'));
cipherChunks.push(decipher.final('utf8'));

return cipherChunks.join('');
}
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
// java
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;

import static java.nio.charset.StandardCharsets.UTF_8;

public class AesUtils {

private static final Base64.Encoder ENCODER = Base64.getEncoder();
private static final Base64.Decoder DECODER = Base64.getDecoder();

public static String aesEncrypt(SecretKey key, String plaintext) throws Exception {
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, key);
byte[] cipherBytes = cipher.doFinal(plaintext.getBytes(UTF_8));
return new String(ENCODER.encode(cipherBytes), UTF_8);
}

public static String aesDecrypt(SecretKey key, String ciphertext) throws Exception {
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, key);
byte[] cipherBytes = DECODER.decode(ciphertext.getBytes(UTF_8));
return new String(cipher.doFinal(cipherBytes), UTF_8);
}

public static SecretKey newAesKey(String keyStr) {
return new SecretKeySpec(DECODER.decode(keyStr.getBytes(UTF_8)), "AES");
}

public static String toAesKeyStr(SecretKey key) {
return new String(ENCODER.encode(key.getEncoded()), UTF_8);
}
}

需使用签名算法 SHA256withRSA,对请求待签字符串和响应待签字符串进行签名,签名结果采用 Base64 编码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// node

/**
* SHA256withRSA 私钥签名
*/
public rsaSign(data: string) {
const sign = crypto.createSign('RSA-SHA256');
sign.update(Buffer.from(data, 'utf8'));
return sign.sign(this.CUSTOMER_PRIVATE_KEY, 'base64');
}
/**
* SHA256withRSA 公钥验签
*/
public rsaVerify(data: string, sign: string) {
const verify = crypto.createVerify('RSA-SHA256');
verify.update(Buffer.from(data, 'utf8'));
return verify.verify(this.TRIP_LINK_PUBLIC_KEY, sign, 'base64');
}
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
// java

import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;

import static java.nio.charset.StandardCharsets.UTF_8;

public class RsaUtils {

private static final Base64.Encoder ENCODER = Base64.getEncoder();
private static final Base64.Decoder DECODER = Base64.getDecoder();

public static String rsaSign(PrivateKey privateKey, String content) throws Exception {
Signature signer = Signature.getInstance("SHA256withRSA");
signer.initSign(privateKey);
signer.update(content.getBytes(UTF_8));
return new String(ENCODERL.encode(signer.sign()), UTF_8);
}

public static boolean rsaVerify(PublicKey publicKey, String content, String signature) throws Exception {
Signature signer = Signature.getInstance("SHA256withRSA");
signer.initVerify(publicKey);
signer.update(content.getBytes(UTF_8));
return signer.verify(DECODER.decode(signature.getBytes(UTF_8)));
}

public static PrivateKey newRsaPrivateKey(String privateKeyStr) throws Exception {
byte[] bytes = DECODER.decode(privateKeyStr.getBytes(UTF_8));
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
return keyFactory.generatePrivate(new PKCS8EncodedKeySpec(bytes));
}

public static PublicKey newRsaPublicKey(String publicKeyStr) throws Exception {
byte[] bytes = DECODER.decode(publicKeyStr.getBytes(UTF_8));
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
return keyFactory.generatePublic(new X509EncodedKeySpec(bytes));
}

public static String toRsaPrivateKeyStr(PrivateKey privateKey) {
return new String(ENCODER.encode(privateKey.getEncoded()), UTF_8);
}

public static String toRsaPublicKeyStr(PublicKey publicKey) {
return new String(ENCODER.encode(publicKey.getEncoded()), UTF_8);
}
}

nodejs crypto 加密 对称加密 非对称加密

文章目录
  1. 1. HMac 算法
  2. 2. 签名和验证算法
  3. 3. HMac 算法 md5
  4. 4. 非对称加密解密 (RSA)
  5. 5. 对称加密 (AES)
  6. 6. 图片文件的文件摘要
  7. 7. 公钥加密(crypto.constants.RSA_PKCS1_OAEP_PADDING)
  8. 8. 私钥解密(crypto.constants.RSA_PKCS1_OAEP_PADDING)
  9. 9. 使用对称加密算法 AES/ECB/PKCS5Padding,对请求体和响应体进行加密与解密,结果采用 Base64 编码。
  10. 10. 需使用签名算法 SHA256withRSA,对请求待签字符串和响应待签字符串进行签名,签名结果采用 Base64 编码。