好習慣,先上代碼再說事
加密
void AesEncrypt(unsigned char* pchIn, int nInLen, unsigned char *ciphertext, int &ciphertext_len, unsigned char * pchKey) { EVP_CIPHER_CTX en; EVP_CIPHER_CTX_init(&en); const EVP_CIPHER *cipher_type; unsigned char *passkey, *passiv, *plaintxt; unsigned char *plaintext = NULL; unsigned char iv[] = { 0x00}; cipher_type = EVP_aes_128_ecb(); EVP_EncryptInit_ex(&en, cipher_type, NULL, pchKey, iv); //當長度正好為16字節的倍數時,同樣需要padding static const int MAX_PADDING_LEN = 16; if(!EVP_EncryptInit_ex(&en, NULL, NULL, NULL, NULL)) { printf("ERROR in EVP_EncryptInit_ex \n"); return; } int bytes_written = 0; ciphertext_len = 0; if(!EVP_EncryptUpdate(&en, ciphertext, &bytes_written, (unsigned char *) pchIn, nInLen) ) { printf("ERROR in EVP_EncryptUpdate \n"); return; } ciphertext_len += bytes_written; if(!EVP_EncryptFinal_ex(&en, ciphertext + bytes_written, &bytes_written)) { printf("ERROR in EVP_EncryptFinal_ex \n"); return; } ciphertext_len += bytes_written; ciphertext[ciphertext_len] = 0; EVP_CIPHER_CTX_cleanup(&en); return; }
解密
void AesDecrypt(unsigned char* pchInPut, int nInl, unsigned char *pchOutPut, unsigned char *pchKey) { unsigned char achIv[8]; EVP_CIPHER_CTX ctx; EVP_CIPHER_CTX_init(&ctx); EVP_DecryptInit_ex(&ctx, EVP_aes_128_ecb(), NULL, pchKey, achIv); int nLen = 0; int nOutl = 0; EVP_DecryptUpdate(&ctx, pchOutPut+nLen, &nOutl, pchInPut+nLen, nInl); nLen += nOutl; EVP_DecryptFinal_ex(&ctx, pchOutPut+nLen, &nOutl); nLen+=nOutl; pchOutPut[nLen]=0; EVP_CIPHER_CTX_cleanup(&ctx); return; }
下面開始說事,純A代碼的下面不用看了。
首先明確以下概念,AES是加密的算法,使用128、192 和 256 位密鑰,將被加密數據划分為128位(16字節)一塊,然后使用某種加密模式進行加密。
1、主要的加密模式有以下幾種:
ECB模式
按照塊密碼的塊大小被分為數個塊,並對每個塊進行獨立加密。
優點:
1.簡單;
2.有利於並行計算;
3.誤差不會被傳送;
缺點:
1.不能隱藏明文的模式;
2.可能對明文進行主動攻擊;
CBC模式:
每個平文塊先與前一個密文塊進行異或后,再進行加密。在這種方法中,每個密文塊都依賴於它前面的所有平文塊。
同時,為了保證每條消息的唯一性,在第一個塊中需要使用初始化向量。
優點:
1.不容易主動攻擊,安全性好於ECB,適合傳輸長度長的報文,是SSL、IPSec的標准。
缺點:
1.不利於並行計算;
2.誤差傳遞;
3.需要初始化向量IV
CFB模式:
模式類似於CBC,可以將塊密碼變為自同步的流密碼。
優點:
1.隱藏了明文模式;
2.分組密碼轉化為流模式;
3.可以及時加密傳送小於分組的數據;
缺點:
1.不利於並行計算;
2.誤差傳送:一個明文單元損壞影響多個單元;
3.唯一的IV;
OFB模式:
可以將塊密碼變成同步的流密碼。它產生密鑰流的塊,然后將其與平文塊進行異或,得到密文。
優點:
1.隱藏了明文模式;
2.分組密碼轉化為流模式;
3.可以及時加密傳送小於分組的數據;
缺點:
1.不利於並行計算;
2.對明文的主動攻擊是可能的;
3.誤差傳送:一個明文單元損壞影響多個單元;
PCBC模式
略
CTR模式
略
在我的代碼中使用的是最簡單的ecb模式。
2、由於被加密數據分組時,有可能不會正好為128bit的整數倍,所以需要padding(填充補齊),而padding模式有以下幾種:
None //不填充。
PKCS7 //填充字符串由一個字節序列組成,每個字節填充該字節序列的長度。
Zeros //填充字符串由設置為零的字節組成。
ANSIX923 //ANSIX923 填充字符串由一個字節序列組成,此字節序列的最后一個字節填充字節序列的長度,其余字節均填充數字零。
ISO10126 //ISO10126 填充字符串由一個字節序列組成,此字節序列的最后一個字節填充字節序列的長度,其余字節填充隨機數據。
根據官方文檔地址(https://www.openssl.org/docs/crypto/EVP_CIPHER_CTX_set_padding.html)中描述:
If padding is enabled (the default) then EVP_EncryptFinal_ex() encrypts the "final" data, that is any data that remains in a partial block. It uses standard block padding (aka PKCS padding) as described in the NOTES section, below. The encrypted final data is written to out which should have sufficient space for one cipher block. The number of bytes written is placed in outl. After this function is called the encryption operation is finished and no further calls to EVP_EncryptUpdate() should be made.
可以知道在調用EVP_EncryptFinal_ex函數時,padding是默認啟用的。
是否使用padding可以通過EVP_CIPHER_CTX_set_padding函數設置,第二個參數為0則禁用padding,為1則啟用padding。
文檔中說padding的模式為PKCS padding,具體指的是PKCS#7。
這里用例子表述一下PKCS#7:
假設塊長度為128bit,也就是16個字節。
那么當需要padding的字符串長度為11時(假設字符源串16進制為 : DD DD DD DD DD DD DD DD | DD DD DD DD ),則padding結果為:
| DD DD DD DD DD DD DD DD | DD DD DD DD 04 04 04 04 |
也就是缺4個字節,就是用04來padding,缺5個,就用05來Padding。
然后再將padding后的字符串進行加密。
我如何知道Padding模式是PKCS#7的?如果你不質疑我的分析,此部分可以跳過。
1.先使用一個key對長度為16的源串(隨意)進行加密
EVP_EncryptFinal_ex函數調用后,得到結果如圖1中紅色部分,
紅色部分就是padding內容的加密結果。
因為在PKCS#7模式下,padding的內容為 | 16 16 16 16 16 16 16 16 | 16 16 16 16 16 16 16 16 |
那么我構造一個字符串,如下圖
經過加密后,如下圖
可以看到此處16進制字符串與上面的紅色字符串一致,所以能說明此處使用的Padding模式為PKCS#7。
為什么要研究這個padding?
1.當在使用openssl庫與其他不同的庫進行通信時,padding與加密模式不同(這都能搞錯?)會導致讓你蛋疼的問題。
2.理論是指導實踐的標准,實踐是檢驗理論的方法。
關於padding更詳細的信息可以參考:
http://en.wikipedia.org/wiki/Padding_(cryptography)
最后hex與char互轉的代碼
char SupperToLower(char in) { if(in >= 65 && in <= 90) return in + 32; else return in; } char LowerToSupper(char in) { if(in >= 97 && in <= 122) return in - 32; else return in; } static const char *tohex = "0123456789ABCDEF"; void CharToHex(unsigned char *pchOut, const unsigned char *pchIn, int nLen) { int i = 0; while(i < nLen) { *pchOut++ = tohex[pchIn[i] >> 4]; *pchOut++ = tohex[pchIn[i] & 0x0F]; ++i; } return; } void HexToChar(unsigned char *pchOut, const unsigned char *pchIn, int nInLen) { int an=1,num=0,st,q,tmp,kk = 0; for(q = nInLen - 1,st = nInLen / 2 - 1; q >= 0,st >= 0; q --) { if(kk==2) { pchOut[st] = (unsigned char)num; st --; num = 0; an = 1; kk = 0; } if(LowerToSupper(pchIn[q]) >= 'A' && LowerToSupper(pchIn[q]) <= 'F') tmp = pchIn[q] - 'A' + 10; else tmp = pchIn[q] - '0'; num += an * tmp; an *= 16; kk ++; } return; }