C++ Java和Node.js之间的AES加解密研究(GCM)

前一篇记录了ECB编码,这篇用GCB编码,区别比想象的大一点。下面是各语言AES/GCM/128的加解密代码,经测试互相之间可通用。供参考(注意CTRL+C的话key变量不要忘了改)

Java把加密数据与TAG连接在一起作为GCM密文,所以为了互相通信,其它语言实现也按这种方式组合。

Node.js 代码

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
crypto = require('crypto');

/**
* aes加密
* @param data
* @param secretKey
*/
aesEncrypt = function(data, secretKey, iv) {
var md5 = crypto.createHash('md5');
var result = md5.update(secretKey).digest();
var cipher = crypto.createCipheriv('aes-128-gcm',result, iv);
const encrypted = cipher.update(data);
const final = cipher.final();
const tag = cipher.getAuthTag();
const res = Buffer.concat([encrypted, final, tag]); // java的GCM加密是把AuthTag合并在一起的
return res.toString('base64');
}

/**
* aes解密
* @param data
* @param secretKey
* @returns {*}
*/
aesDecrypt = function(encrypted, secretKey, iv ) {
var md5 = crypto.createHash('md5');
var result = md5.update(secretKey).digest();
var decipher = crypto.createDecipheriv('aes-128-gcm',result, iv);

var b = new Buffer(encrypted, 'base64')
decipher.setAuthTag(b.subarray(b.length - 16));
return decipher.update(b.subarray(0, b.length-16)) + decipher.final('utf8');
}

const data = "Across the Great Wall we can reach every corner in the world.";
const key = "vwOr2r";
const iv = "0123456789ABCDEF";
let encryptStr = aesEncrypt(data, key, iv);
console.log( encryptStr );
let out = aesDecrypt(encryptStr, key, iv);
console.log(out);

Java/Android 代码

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
52
53
public class AES {
//算法
private static final String ALGORITHMSTR = "AES/GCM/NoPadding";
public static final String DEFAULT_CODING = "utf-8";

/**
* AES加密
* @param content 待加密的内容
* @param encryptKey 加密密钥
* @return 加密后的byte[]
*/
public static byte[] aesEncryptToBytes(String content, String encryptKey, byte[] iv) throws Exception {
byte[] input = content.getBytes(DEFAULT_CODING);

MessageDigest md = MessageDigest.getInstance("MD5");
byte[] thedigest = md.digest(encryptKey.getBytes(DEFAULT_CODING));

SecretKeySpec skc = new SecretKeySpec(thedigest, "AES");

IvParameterSpec ivspec = new IvParameterSpec(iv);

Cipher cipher = Cipher.getInstance(ALGORITHMSTR);
cipher.init(Cipher.ENCRYPT_MODE, skc, ivspec);

byte[] cipherText = new byte[cipher.getOutputSize(input.length)];
int ctLength = cipher.update(input, 0, input.length, cipherText, 0);
ctLength += cipher.doFinal(cipherText, ctLength);

return cipherText;
}

/**
* AES解密
* @param encryptBytes 待解密的byte[]
* @param decryptKey 解密密钥
* @return 解密后的String
*/
public static String aesDecryptByBytes(byte[] encryptBytes, String decryptKey, byte[] iv) throws Exception {
byte[] keyb = decryptKey.getBytes(DEFAULT_CODING);
MessageDigest md = MessageDigest.getInstance("MD5");

byte[] thedigest = md.digest(keyb);
SecretKeySpec skey = new SecretKeySpec(thedigest, "AES");

IvParameterSpec ivspec = new IvParameterSpec(iv);

Cipher dcipher = Cipher.getInstance(ALGORITHMSTR);
dcipher.init(Cipher.DECRYPT_MODE, skey, ivspec);

byte[] clearbyte = dcipher.doFinal(encryptBytes);
return new String(clearbyte);
}
}

在Activity中测试

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
52
53
54
55
56
57
58
59
60
package com.example.myapplication;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.util.Base64;

public class MainActivity extends AppCompatActivity {

private static final String key = "vwOr2r";
private static final String iv = "0123456789ABCDEF";

private Button buttonEncrypt;
private Button buttonDecrypt;
private EditText viewInput;
private EditText viewOutput;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
buttonEncrypt = findViewById(R.id.buttonEncrypt);
buttonDecrypt = findViewById(R.id.buttonDecrypt);
viewInput = findViewById(R.id.textViewInput);
viewOutput = findViewById(R.id.textViewOutput);

// 从viewInput的输入框中取得数据,加密,转成base64,显示到viewOutput中
buttonEncrypt.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
try {
byte[] encrypted_data;
String s = viewInput.getText().toString();
encrypted_data = AES.aesEncryptToBytes(s, key, iv);
String encrypted_data_base64 = Base64.encodeToString(encrypted_data, 0);
viewOutput.setText(encrypted_data_base64);
} catch (Exception e) {
e.printStackTrace();
}
}
});

// 从viewInput的输入框中取得数据,先base64解码,再解密,结果显示到viewOutput中
buttonDecrypt.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
try {
String s = viewInput.getText().toString();
byte[] src_data = Base64.decode(s, 0);
String decrypted_data = AES.aesDecryptByBytes(src_data, key, iv);
viewOutput.setText(decrypted_data);
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
}

C++代码 OpenSSL版

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
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
#include <string>
#include <vector>
#include <openssl/evp.h>
#include <openssl/md5.h>

using namespace std;

string g_data = "Across the Great Wall we can reach every corner in the world.";
string g_key = "vwOr2r";
string g_iv = "0123456789ABCDEF";

void handleErrors()
{
exit(-1);
}

int encrypt(const unsigned char *plaintext, int plaintext_len,
const unsigned char *key, const unsigned char *iv,
unsigned char *tag,
unsigned char *ciphertext)
{
EVP_CIPHER_CTX *ctx;
int len;
int ciphertext_len;
int ret;

if (!(ctx = EVP_CIPHER_CTX_new())) handleErrors();

if (!EVP_EncryptInit_ex(ctx, EVP_aes_128_gcm(), NULL, NULL, NULL))
handleErrors();

if (!EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, 16, NULL))
handleErrors();

if (!EVP_EncryptInit_ex(ctx, NULL, NULL, key, iv)) handleErrors();


if (!EVP_EncryptUpdate(ctx, ciphertext, &len, plaintext, plaintext_len))
handleErrors();
ciphertext_len = len;

ret = EVP_EncryptFinal_ex(ctx, ciphertext + len, &len);

if (!EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_GET_TAG, 16, tag))
handleErrors();

EVP_CIPHER_CTX_free(ctx);

if (ret > 0)
{
ciphertext_len += len;
return ciphertext_len;
}
else
{
return -1;
}
}

int decrypt(const unsigned char *ciphertext, int ciphertext_len,
const unsigned char *key, const unsigned char *iv,
const unsigned char *tag,
unsigned char *plaintext)
{
EVP_CIPHER_CTX *ctx;
int len;
int plaintext_len;
int ret;

if (!(ctx = EVP_CIPHER_CTX_new())) handleErrors();

if (!EVP_DecryptInit_ex(ctx, EVP_aes_128_gcm(), NULL, NULL, NULL))
handleErrors();

if (!EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, 16, NULL))
handleErrors();

if (!EVP_DecryptInit_ex(ctx, NULL, NULL, key, iv)) handleErrors();

if (!EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_TAG, 16, (void*)tag))
handleErrors();

if (!EVP_DecryptUpdate(ctx, plaintext, &len, ciphertext, ciphertext_len))
handleErrors();
plaintext_len = len;

ret = EVP_DecryptFinal_ex(ctx, plaintext + len, &len);

EVP_CIPHER_CTX_free(ctx);

if (ret > 0)
{
plaintext_len += len;
return plaintext_len;
}
else
{
return -1;
}
}

typedef std::vector<uint8_t> byte_array;
std::string base64Encode(byte_array data)
{
if (data.empty()) return {};

std::string res;
res.resize((data.size() / 3 + 1) * 4);
int len = EVP_EncodeBlock((unsigned char *)&res[0], &data[0], data.size());
res.resize(len);
return res;
}

// base64 解码,用EVP_DecodeBlock不去除补充字符,还是多写两行
byte_array base64Decode(std::string data)
{
int len = data.length();
if (len == 0) return {};

byte_array res;
int outlen = 0;
int tmp = 0;

res.resize(len);

EVP_ENCODE_CTX ctx;
EVP_DecodeInit(&ctx);
EVP_DecodeUpdate(&ctx, &res[0], &tmp, (unsigned char *)&data[0], len);
outlen = tmp;
if (EVP_DecodeFinal(&ctx, &res[outlen], &tmp) <= 0) return {};
outlen += tmp;

res.resize(outlen);
return res;
}

int main()
{
unsigned char key[16] = { 0 };

int inLen = g_data.length();
int encLen = 0;
int outlen = 0;
unsigned char encData[1024];

MD5((unsigned char*)g_key.c_str(), g_key.length(), key);

// 加密测试
{
unsigned char tag[16] = { 0 };
encLen = encrypt((const unsigned char*)&g_data[0], inLen,
key, (const unsigned char*)&g_iv[0], tag, encData);

if (encLen > 0) {
std::copy_n(tag, 16, encData + encLen);
encLen += 16;
std::cout << base64Encode(byte_array{ encData, encData + encLen }) << std::endl;
}
}

// 解密测试
{
unsigned char iv[16];
char decData[1024];
std::copy(g_iv.begin(), g_iv.end(), iv);

int decLen = decrypt(
encData, encLen - 16,
key, iv,
encData + encLen - 16,
(unsigned char*)decData);

if (decLen > 0) {
std::cout << std::string{ decData, decData + decLen } << std::endl;
}
}

return 0;
}