openssl java aes_Java AES算法和OpenSSL配对
近日工作上的原因,需要實現(xiàn)Java? AES算法和C語言下基于OpenSSL的AES 算法通信。這是個老問題了,網(wǎng)上搜到不少資料,但都不是很詳細,沒能解決問題。只能自己來了。
先說說AES算法。AES算法的實現(xiàn)有四種,如CBC/ECB/CFB/OFB,這四種Java和C都有實現(xiàn)。AES算法還有末尾的填充(padding),java支持的padding方式有三種NoPadding/PKCS5Padding/,而C卻不能顯式的設置padding方式,默認的padding就是在末尾加 '\0'。這是一個大坑,多少人都坑在這了。另外,網(wǎng)上很多JAVA AES算法,很多都用SecureRandom,如果你的代碼中出現(xiàn)了SecureRandom這個東西,那么你再也不能用C解出來了。
先說Java端的。從良心上說,java的封裝比C要強多了。先上代碼:
public static String encrypt(String content, String passwd) {
try {
Cipher aesECB = Cipher.getInstance("AES/ECB/PKCS5Padding");
SecretKeySpec key = new SecretKeySpec(passwd.getBytes(), "AES");
aesECB.init(Cipher.ENCRYPT_MODE, key);
byte[] result = aesECB.doFinal(content.getBytes());
return new BASE64Encoder().encode(result);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (NoSuchPaddingException e) {
e.printStackTrace();
} catch (InvalidKeyException e) {
e.printStackTrace();
} catch (IllegalBlockSizeException e) {
e.printStackTrace();
} catch (BadPaddingException e) {
e.printStackTrace();
}
return null;
}
public String decrypt(String content, String passwd) {
try {
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");// 創(chuàng)建密碼器
SecretKeySpec key = new SecretKeySpec(passwd.getBytes(), "AES");
cipher.init(Cipher.DECRYPT_MODE, key);// 初始化
byte[] result = new BASE64Decoder().decodeBuffer(content);
return new String(cipher.doFinal(result)); // 解密
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (NoSuchPaddingException e) {
e.printStackTrace();
} catch (InvalidKeyException e) {
e.printStackTrace();
} catch (IllegalBlockSizeException e) {
e.printStackTrace();
} catch (BadPaddingException e) {
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}
以上就是兩個加密解密函數(shù),默認使用AES算法的ECB,填充方式選擇了PKCS5Padding。中間用到了Base64算法將加密后的字串進行再加密,主要是為了可視化讀和傳遞。使用Base64算法要引用sun.misc.BASE64Decoder和sun.misc.BASE64Encoder;
Java就是這么簡單,當然它一開始并沒有這么簡單,我也是從SecureRandom里面跳出來的。
關于openssl庫,先看EVP。EVP是OpenSSL自定義的一組高層算法封裝函數(shù),它是對具體算法的封裝。使得可以在同一類加密算法框架下,通過相同的接口去調(diào)用不同的加密算法或者便利地改變具體的加密算法,這樣大提高 了代碼的可重用性。當你使用EVP的時候你就會發(fā)現(xiàn),它的使用方法和Java是那么的相似,以至于會產(chǎn)生他們的結果肯定會相同的遐想。在使用它之前,我們先來學習學些它的用法。我們這里取出了幾個重要的函數(shù)列在下面:
【EVP_CIPHER_CTX_init】
該函數(shù)初始化一個EVP_CIPHER_CTX結構體,只有初始化后該結構體才能在下面介紹的函數(shù)中使用。操作成功返回1,否則返回0。
【EVP_EncryptInit_ex】
該函數(shù)采用ENGINE參數(shù)impl的算法來設置并初始化加密結構體。其中,參數(shù)ctx必須在調(diào)用本函數(shù)之前已經(jīng)進行了初始化。參數(shù)type通常通過函數(shù)類型來提供參數(shù),如EVP_des_cbc函數(shù)的形式,即我們上一章中介紹的對稱加密算法的類型。如果參數(shù)impl為NULL,那么就會使用缺省的實現(xiàn)算法。參數(shù)key是用來加密的對稱密鑰,iv參數(shù)是初始化向量(如果需要的話)。在算法中真正使用的密鑰長度和初始化密鑰長度是根據(jù)算法來決定的。在調(diào)用該函數(shù)進行初始化的時候,除了參數(shù)type之外,所有其它參數(shù)可以設置為NULL,留到以后調(diào)用其它函數(shù)的時候再提供,這時候參數(shù)type就設置為NULL就可以了。在缺省的加密參數(shù)不合適的時候,可以這樣處理。操作成功返回1,否則返回0。
【EVP_EncryptUpdate】
該函數(shù)執(zhí)行對數(shù)據(jù)的加密。該函數(shù)加密從參數(shù)in輸入的長度為inl的數(shù)據(jù),并將加密好的數(shù)據(jù)寫入到參數(shù)out里面去。可以通過反復調(diào)用該函數(shù)來處理一個連續(xù)的數(shù)據(jù)塊。寫入到out的數(shù)據(jù)數(shù)量是由已經(jīng)加密的數(shù)據(jù)的對齊關系決定的,理論上來說,從0到(inl+cipher_block_size-1)的任何一個數(shù)字都有可能(單位是字節(jié)),所以輸出的參數(shù)out要有足夠的空間存儲數(shù)據(jù)。寫入到out中的實際數(shù)據(jù)長度保存在outl參數(shù)中。操作成功返回1,否則返回0。
【EVP_EncryptFinal_ex】
該函數(shù)處理最后(Final)的一段數(shù)據(jù)。在函數(shù)在padding功能打開的時候(缺省)才有效,這時候,它將剩余的最后的所有數(shù)據(jù)進行加密處理。該算法使用標志的塊padding方式(AKA PKCS padding)。加密后的數(shù)據(jù)寫入到參數(shù)out里面,參數(shù)out的長度至少應該能夠一個加密塊。寫入的數(shù)據(jù)長度信息輸入到outl參數(shù)里面。該函數(shù)調(diào)用后,表示所有數(shù)據(jù)都加密完了,不應該再調(diào)用EVP_EncryptUpdate函數(shù)。如果沒有設置padding功能,那么本函數(shù)不會加密任何數(shù)據(jù),如果還有剩余的數(shù)據(jù),那么就會返回錯誤信息,也就是說,這時候數(shù)據(jù)總長度不是塊長度的整數(shù)倍。操作成功返回1,否則返回0。
PKCS padding標準是這樣定義的,在被加密的數(shù)據(jù)后面加上n個值為n的字節(jié),使得加密后的數(shù)據(jù)長度為加密塊長度的整數(shù)倍。無論在什么情況下,都是要加上padding的,也就是說,如果被加密的數(shù)據(jù)已經(jīng)是塊長度的整數(shù)倍,那么這時候n就應該等于塊長度。比如,如果塊長度是9,要加密的數(shù)據(jù)長度是11,那么5個值為5的字節(jié)就應該增加在數(shù)據(jù)的后面。
【EVP_DecryptInit_ex, EVP_DecryptUpdate和EVP_DecryptFinal_ex】
這三個函數(shù)是上面三個函數(shù)相應的解密函數(shù)。這些函數(shù)的參數(shù)要求基本上都跟上面相應的加密函數(shù)相同。如果padding功能打開了,EVP_DecryptFinal會檢測最后一段數(shù)據(jù)的格式,如果格式不正確,該函數(shù)會返回錯誤代碼。此外,如果打開了padding功能,EVP_DecryptUpdate函數(shù)的參數(shù)out的長度應該至少為(inl+cipher_block_size)字節(jié);但是,如果加密塊的長度為1,則其長度為inl字節(jié)就足夠了。三個函數(shù)都是操作成功返回1,否則返回0。
需要注意的是,雖然在padding功能開啟的情況下,解密操作提供了錯誤檢測功能,但是該功能并不能檢測輸入的數(shù)據(jù)或密鑰是否正確,所以即便一個隨機的數(shù)據(jù)塊也可能無錯的完成該函數(shù)的調(diào)用。如果padding功能關閉了,那么當解密數(shù)據(jù)長度是塊長度的整數(shù)倍時,操作總是返回成功的結果。
前面我們說過,openssl的填充padding方式不能自定義,之后采用默認的在尾端加字符'\0',但是EVP會默認打開Padding,且使用的Padding方式為PKCS padding,所以只要java使用對應的填充方式,理論上加解密的結果是一樣的。知道了這些函數(shù),如何使用呢?上個代碼(整理后的):
void encrypt(unsigned char* in, int inl, unsigned char *out, int* len, unsigned char * key){
unsigned char iv[8];
EVP_CIPHER_CTX ctx;
//此init做的僅是將ctx內(nèi)存 memset為0
EVP_CIPHER_CTX_init(&ctx);
//cipher? = EVP_aes_128_ecb();
//原型為int EVP_EncryptInit_ex(EVP_CIPHER_CTX *ctx,const EVP_CIPHER *cipher, ENGINE *impl, const unsigned char *key, const unsigned char *iv)
//另外對于ecb電子密碼本模式來說,各分組獨立加解密,前后沒有關系,也用不著iv
EVP_EncryptInit_ex(&ctx, EVP_aes_128_ecb(), NULL, key, iv);
*len = 0;
int outl = 0;
//這個EVP_EncryptUpdate的實現(xiàn)實際就是將in按照inl的長度去加密,實現(xiàn)會取得該cipher的塊大小(對aes_128來說是16字節(jié))并將block-size的整數(shù)倍去加密。
//如果輸入為50字節(jié),則此處僅加密48字節(jié),outl也為48字節(jié)。輸入in中的最后兩字節(jié)拷貝到ctx->buf緩存起來。
//對于inl為block_size整數(shù)倍的情形,且ctx->buf并沒有以前遺留的數(shù)據(jù)時則直接加解密操作,省去很多后續(xù)工作。
EVP_EncryptUpdate(&ctx, out+*len, &outl, in+*len, inl);
*len+=outl;
//余下最后n字節(jié)。此處進行處理。
//如果不支持pading,且還有數(shù)據(jù)的話就出錯,否則,將block_size-待處理字節(jié)數(shù)個數(shù)個字節(jié)設置為此個數(shù)的值,如block_size=16,數(shù)據(jù)長度為4,則將后面的12字節(jié)設置為16-4=12,補齊為一個分組后加密
//對于前面為整分組時,如輸入數(shù)據(jù)為16字節(jié),最后再調(diào)用此Final時,不過是對16個0進行加密,此密文不用即可,也根本用不著調(diào)一下這Final。
int test = inl>>4;
if(inl != test<<4){
EVP_EncryptFinal_ex(&ctx,out+*len,&outl);
*len+=outl;
}
EVP_CIPHER_CTX_cleanup(&ctx);
}
總結
以上是生活随笔為你收集整理的openssl java aes_Java AES算法和OpenSSL配对的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 手电筒java_Java鼠标“手电筒”效
- 下一篇: java 保存inputstream_j