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 加密 对称加密 非对称加密