0%

RSA和AES的golang和c++版本使用

概要: 本文从golang语言和c++语言的角度使用RSA和AES,从中总结需要注意的事项,
RSA的私钥格式区别:PKCS#1 和 PKCS#8是有区别的,AES的加密工作机制需要加密端和
被加密端的模式相同

RSA

RSA的私钥格式区别:PKCS#1 和 PKCS#8

PKCS#1的格式文件开头是以

1
2
-----BEGIN RSA PRIVATE KEY-----
-----END RSA PRIVATE KEY-----

PKCS#8的格式是以下开头
1
2
-----BEGIN PUBLIC KEY-----
-----END PUBLIC KEY-----

使用openssl库加载公私钥时的接口有所区别:

PKCS#1 使用接口:PEM_read_bio_RSAPublicKey()函数
PKCS#8 使用接口:PEM_read_bio_RSA_PUBKEY()函数

PKCS#1和PKCS#8的转换命令

1
2
3
4
5
6
7
8
9
10
11
12
13
# 私钥生成两种格式
openssl genrsa -out private.pem 4096 # 这是生成包含公私钥匙的pem文件 格式为PKCS#1
openssl rsa -in private.pem -pubout -out public.txt #从私钥中生成公钥 默认格式为PKCS#8
openssl rsa -in private.pem -RSAPublicKey_out -out public.pem #从私钥中生成公钥 默认格式为PKCS#1

# 私钥格式互转
openssl pkcs8 -topk8 -inform PEM -in private.pem -outform pem -nocrypt -out private.txt #将私钥PKCS#1转成PKCS#8
openssl rsa -in private.txt -out private.pem #PKCS8格式私钥再转换为PKCS1格式

# 公钥格式互转
openssl rsa -pubin -in public.pem -RSAPublicKey_out #pkcs8公钥转pkcs1公钥
openssl rsa -RSAPublicKey_in -in pub_pkcs1.pem -pubout #pkcs1公钥转换为pkcs8公钥

AES

AES的加密工作机制共有5种

  • 1.电码本模式(Electronic Codebook Book (ECB));
  • 2.密码分组链接模式(Cipher Block Chaining (CBC));
  • 3.计算器模式(Counter (CTR));
  • 4.密码反馈模式(Cipher FeedBack (CFB));
  • 5.输出反馈模式(Output FeedBack (OFB))。

加密端和解密端的工作机制要一致,所以使用AES时需要理解算法使用的那种工作模式

AES加密与解密的padding问题

首先说一下,在AES加密与解密的过程中如果需要加密的数据不是16的倍数的时候,需要对原来的数据做padding操作。
padding分为以下几种,不同的padding对加密与解密有影响,所以要保证padding的方式是一致的。

  1. ANSI X.923
    在ANSI X.923的方式下,先是填充00,最后一个字节填充padded的字节个数。
    例子: | DD DD DD DD DD DD DD DD | DD DD DD 00 00 00 00 05 |
  2. ISO 10126
    在ISO 10126的方式下,先是填充随机值,最后一个字节填充padded的字节个数。
    例子: | DD DD DD DD DD DD DD DD | DD DD DD 95 81 28 A7 05 |
  3. PKCS7
    在PKCS7的方式下,如果一共需要padded多少个字节,所有填充的地方都填充这个值。
    例子: | DD DD DD DD DD DD DD DD | DD DD DD 05 05 05 05 05 |
  4. ISO/IEC 7816-4
    在ISO/IEC 7816-4方式下,第一个填充的字节是80,后面的都填充00。
    例子: | DD DD DD DD DD DD DD DD | DD DD DD 80 00 00 00 00 |
  5. Zero padding
    在Zero padding方式下,每一个需要填充的字节都填00。
    例子: | DD DD DD DD DD DD DD DD | DD DD DD 00 00 00 00 00 |

代码实现

RSA的c++的加解密

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include "openssl/pem.h"
// 填充格式
int padding = RSA_PKCS1_PADDING;

RSA* createRSA(unsigned char* key, int flag)
{
// ...
// 这里注意读取公私钥的格式时使用的接口,这里使用的PKCS#8格式
if(flag)
rsa = PEM_read_bio_RSA_PUBKEY(keybio, &rsa, NULL, NULL);
else
rsa = PEM_read_bio_RSAPrivateKey(keybio, &rsa, NULL, NULL);
// ...
}

使用openssl的rsa接口

1
2
3
4
5
6
#include "openssl/rsa.h"

int result = RSA_public_encrypt(data_len, data, encrypted, rsa, padding);
int result = RSA_public_decrypt(data_len, enc_data, decrypted, rsa, padding);
int result = RSA_private_decrypt(data_len,enc_data,decrypted,rsa,padding);
int result = RSA_private_encrypt(data_len, data, encrypted, rsa, padding);

RSA的golang的加解密

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

// 加密
func RsaEncrypt(origData []byte) ([]byte, error) {
// "encoding/pem" 使用pem来解析PKCS1格式的pem
block, _ := pem.Decode(publicKey)
if block == nil {
return nil, errors.New("public key error")
}
pubInterface, err := x509.ParsePKIXPublicKey(block.Bytes)
if err != nil {
return nil, err
}
pub := pubInterface.(*rsa.PublicKey)
//"crypto/rsa" 包来加密信息
return rsa.EncryptPKCS1v15(rand.Reader, pub, origData)
}

// 解密
func RsaDecrypt(ciphertext []byte) ([]byte, error) {
block, _ := pem.Decode(privateKey)
if block == nil {
return nil, errors.New("private key error!")
}
priv, err := x509.ParsePKCS1PrivateKey(block.Bytes)
if err != nil {
return nil, err
}
return rsa.DecryptPKCS1v15(rand.Reader, priv, ciphertext)
}

AES的golang的加解密

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
// 这里的分组模式是如果正好16字节时要多一个16字节(共128位)
// 填充模式就是PKCS7
// 这里要理解一下aes.NewCipher 实现的是AES中的ECB模式
// 如果你要使用CBC模式,需要使用 cipher.NewCBCEncrypter
// 在golang的cipher包中有各种模式的具体实现,可以扒扒代码
func AesEncryptECB(origData []byte, key []byte) (encrypted []byte) {
cipher, _ := aes.NewCipher(generateKey(key))
contents_size := len(origData)
length := contents_size + aes.BlockSize - (contents_size % aes.BlockSize)
plain := make([]byte, length)
copy(plain, origData)
pad := byte(len(plain) - len(origData))
for i := len(origData); i < len(plain); i++ {
plain[i] = pad
}
encrypted = make([]byte, len(plain))
// 分组分块加密
for bs, be := 0, cipher.BlockSize(); bs <= len(origData); bs, be = bs+cipher.BlockSize(), be+cipher.BlockSize() {
cipher.Encrypt(encrypted[bs:be], plain[bs:be])
}

return encrypted
}

// 解密的时候要截取掉过多的填充部分
func AesDecryptECB(encrypted []byte, key []byte) (decrypted []byte) {
cipher, _ := aes.NewCipher(generateKey(key))
decrypted = make([]byte, len(encrypted))
//
for bs, be := 0, cipher.BlockSize(); bs < len(encrypted); bs, be = bs+cipher.BlockSize(), be+cipher.BlockSize() {
cipher.Decrypt(decrypted[bs:be], encrypted[bs:be])
}

trim := 0
if len(decrypted) > 0 {
trim = len(decrypted) - int(decrypted[len(decrypted)-1])
}

return decrypted[:trim]
}

AES的c++的加解密

接口

1
2
3
4
5
6
7
8
9
10
AES_set_encrypt_key(unsigned char*)key, int, &AES_KEY)  // 加密密钥
AES_set_decrypt_key(unsigned char*)key, int, &AES_KEY) // 解密密钥

//因为是对称加密所以加解密接口都用一个
void AES_ecb_encrypt(const unsigned char *in, unsigned char *out,
const AES_KEY *key, const int enc);

void AES_cbc_encrypt(const unsigned char *in, unsigned char *out,
size_t length, const AES_KEY *key,
unsigned char *ivec, const int enc);

参考资料: