歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
您现在的位置: Linux教程網 >> UnixLinux >  >> Linux編程 >> Linux編程

Java AES算法和OpenSSL配對

近日工作上的原因,需要實現Java  AES算法和C語言下基於OpenSSL的AES 算法通信。這是個老問題了,網上搜到不少資料,但都不是很詳細,沒能解決問題。只能自己來了。

先說說AES算法。AES算法的實現有四種,如CBC/ECB/CFB/OFB,這四種Java和C都有實現。AES算法還有末尾的填充(padding),java支持的padding方式有三種NoPadding/PKCS5Padding/,而C卻不能顯式的設置padding方式,默認的padding就是在末尾加 '\0'。這是一個大坑,多少人都坑在這了。另外,網上很多JAVA AES算法,很多都用SecureRandom,如果你的代碼中出現了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");// 創建密碼器
            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();
        <span></span>} 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;
    }

以上就是兩個加密解密函數,默認使用AES算法的ECB,填充方式選擇了PKCS5Padding。中間用到了Base64算法將加密後的字串進行再加密,主要是為了可視化讀和傳遞。使用Base64算法要引用sun.misc.BASE64Decoder和sun.misc.BASE64Encoder;

Java就是這麼簡單,當然它一開始並沒有這麼簡單,我也是從SecureRandom裡面跳出來的。

關於openssl庫,先看EVP。EVP是OpenSSL自定義的一組高層算法封裝函數,它是對具體算法的封裝。使得可以在同一類加密算法框架下,通過相同的接口去調用不同的加密算法或者便利地改變具體的加密算法,這樣大提高 了代碼的可重用性。當你使用EVP的時候你就會發現,它的使用方法和Java是那麼的相似,以至於會產生他們的結果肯定會相同的遐想。在使用它之前,我們先來學習學些它的用法。我們這裡取出了幾個重要的函數列在下面:

【EVP_CIPHER_CTX_init】

該函數初始化一個EVP_CIPHER_CTX結構體,只有初始化後該結構體才能在下面介紹的函數中使用。操作成功返回1,否則返回0。

【EVP_EncryptInit_ex】

該函數采用ENGINE參數impl的算法來設置並初始化加密結構體。其中,參數ctx必須在調用本函數之前已經進行了初始化。參數type通常通過函數類型來提供參數,如EVP_des_cbc函數的形式,即我們上一章中介紹的對稱加密算法的類型。如果參數impl為NULL,那麼就會使用缺省的實現算法。參數key是用來加密的對稱密鑰,iv參數是初始化向量(如果需要的話)。在算法中真正使用的密鑰長度和初始化密鑰長度是根據算法來決定的。在調用該函數進行初始化的時候,除了參數type之外,所有其它參數可以設置為NULL,留到以後調用其它函數的時候再提供,這時候參數type就設置為NULL就可以了。在缺省的加密參數不合適的時候,可以這樣處理。操作成功返回1,否則返回0。

【EVP_EncryptUpdate】

該函數執行對數據的加密。該函數加密從參數in輸入的長度為inl的數據,並將加密好的數據寫入到參數out裡面去。可以通過反復調用該函數來處理一個連續的數據塊。寫入到out的數據數量是由已經加密的數據的對齊關系決定的,理論上來說,從0到(inl+cipher_block_size-1)的任何一個數字都有可能(單位是字節),所以輸出的參數out要有足夠的空間存儲數據。寫入到out中的實際數據長度保存在outl參數中。操作成功返回1,否則返回0。

【EVP_EncryptFinal_ex】

該函數處理最後(Final)的一段數據。在函數在padding功能打開的時候(缺省)才有效,這時候,它將剩余的最後的所有數據進行加密處理。該算法使用標志的塊padding方式(AKA PKCS padding)。加密後的數據寫入到參數out裡面,參數out的長度至少應該能夠一個加密塊。寫入的數據長度信息輸入到outl參數裡面。該函數調用後,表示所有數據都加密完了,不應該再調用EVP_EncryptUpdate函數。如果沒有設置padding功能,那麼本函數不會加密任何數據,如果還有剩余的數據,那麼就會返回錯誤信息,也就是說,這時候數據總長度不是塊長度的整數倍。操作成功返回1,否則返回0。

PKCS padding標准是這樣定義的,在被加密的數據後面加上n個值為n的字節,使得加密後的數據長度為加密塊長度的整數倍。無論在什麼情況下,都是要加上padding的,也就是說,如果被加密的數據已經是塊長度的整數倍,那麼這時候n就應該等於塊長度。比如,如果塊長度是9,要加密的數據長度是11,那麼5個值為5的字節就應該增加在數據的後面。

【EVP_DecryptInit_ex, EVP_DecryptUpdate和EVP_DecryptFinal_ex】

這三個函數是上面三個函數相應的解密函數。這些函數的參數要求基本上都跟上面相應的加密函數相同。如果padding功能打開了,EVP_DecryptFinal會檢測最後一段數據的格式,如果格式不正確,該函數會返回錯誤代碼。此外,如果打開了padding功能,EVP_DecryptUpdate函數的參數out的長度應該至少為(inl+cipher_block_size)字節;但是,如果加密塊的長度為1,則其長度為inl字節就足夠了。三個函數都是操作成功返回1,否則返回0。

需要注意的是,雖然在padding功能開啟的情況下,解密操作提供了錯誤檢測功能,但是該功能並不能檢測輸入的數據或密鑰是否正確,所以即便一個隨機的數據塊也可能無錯的完成該函數的調用。如果padding功能關閉了,那麼當解密數據長度是塊長度的整數倍時,操作總是返回成功的結果。

前面我們說過,openssl的填充padding方式不能自定義,之後采用默認的在尾端加字符'\0',但是EVP會默認打開Padding,且使用的Padding方式為PKCS padding,所以只要java使用對應的填充方式,理論上加解密的結果是一樣的。知道了這些函數,如何使用呢?上個代碼(整理後的):

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內存 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的實現實際就是將in按照inl的長度去加密,實現會取得該cipher的塊大小(對aes_128來說是16字節)並將block-size的整數倍去加密。
    //如果輸入為50字節,則此處僅加密48字節,outl也為48字節。輸入in中的最後兩字節拷貝到ctx->buf緩存起來。 
    //對於inl為block_size整數倍的情形,且ctx->buf並沒有以前遺留的數據時則直接加解密操作,省去很多後續工作。 
    EVP_EncryptUpdate(&ctx, out+*len, &outl, in+*len, inl);
    *len+=outl;
    //余下最後n字節。此處進行處理。
    //如果不支持pading,且還有數據的話就出錯,否則,將block_size-待處理字節數個數個字節設置為此個數的值,如block_size=16,數據長度為4,則將後面的12字節設置為16-4=12,補齊為一個分組後加密
    //對於前面為整分組時,如輸入數據為16字節,最後再調用此Final時,不過是對16個0進行加密,此密文不用即可,也根本用不著調一下這Final。
    int test = inl>>4;
    if(inl != test<<4){
        EVP_EncryptFinal_ex(&ctx,out+*len,&outl); 
        *len+=outl;
    }
    EVP_CIPHER_CTX_cleanup(&ctx);
}

更多詳情見請繼續閱讀下一頁的精彩內容: http://www.linuxidc.com/Linux/2015-01/112068p2.htm

Copyright © Linux教程網 All Rights Reserved