2015年2月9日 星期一

使用 Openssl / C++ Crypto++ (Cryptopp) 進行 AES-128 / AES-256 Encryption @ Ubutnu 14.04

去年在一些服務上有用到 AES Encryption,當時是用 PHP 處理的 :P 最近想說 openssl 很威,練一下好了 XD 然而 AES 有很多模式 Orz 直接用 openssl -h 就可以觀看

$ openssl -h
aes-128-cbc       aes-128-ecb       aes-192-cbc       aes-192-ecb    
aes-256-cbc       aes-256-ecb       base64            bf              
bf-cbc            bf-cfb            bf-ecb            bf-ofb          
camellia-128-cbc  camellia-128-ecb  camellia-192-cbc  camellia-192-ecb
camellia-256-cbc  camellia-256-ecb  cast              cast-cbc        
cast5-cbc         cast5-cfb         cast5-ecb         cast5-ofb      
des               des-cbc           des-cfb           des-ecb        
des-ede           des-ede-cbc       des-ede-cfb       des-ede-ofb    
des-ede3          des-ede3-cbc      des-ede3-cfb      des-ede3-ofb    
des-ofb           des3              desx              idea            
idea-cbc          idea-cfb          idea-ecb          idea-ofb        
rc2               rc2-40-cbc        rc2-64-cbc        rc2-cbc        
rc2-cfb           rc2-ecb           rc2-ofb           rc4            
rc4-40            seed              seed-cbc          seed-cfb        
seed-ecb          seed-ofb          zlib


一開始還真不知要挑個 :P 並且用 openssl 加密出來,在其他程式語言卻解不出來 Orz

$ echo -n "Hello World" | openssl aes-128-cbc -e -k "password" -out /tmp/output
$ openssl aes-128-cbc -d -k "password" -in /tmp/output
Hello World


後來才找到這篇文章:
openssl: recover key and IV by passphrase
http://security.stackexchange.com/questions/29106/openssl-recover-key-and-iv-by-passphrase,講得非常清楚,簡言之:

The encryption format used by OpenSSL is non-standard: it is "what OpenSSL does", and if all versions of OpenSSL tend to agree with each other, there is still no reference document which describes this format except OpenSSL source code.

例如加上 -P 可以印出細部訊息:

$ openssl aes-128-cbc -e -k "password" -P
salt=EDC6E641C29F08F0
key=2CBD7689AEB5880E13803871A1DCDDD5
iv =FFFFBF58B814E180882029109B437D74


每次執行 salt, key 和 iv 都會一直變動,所以,若要用 Openssl 驗證時,需要一口氣填好 key 跟 iv 資訊:

$ openssl aes-128-cbc -e -K '' -iv '' -P
salt=0100000000000000
key=00000000000000000000000000000000
iv =00000000000000000000000000000000


當指定 -K 跟 -iv 時,就不會出現亂跳的情況,同理,在其他程式語言(此例以 C++ 為例),就能夠順利加解密。別忘了 -K 跟 -iv 需要 HEX String 格式的參數,在 Unix-like 系統可以透過 echo, od 跟 sed 來辦事:

$ echo -n "0123456789012345" | od -A n -t x1 | sed 's/ //g'
30313233343536373839303132333435
$ openssl aes-128-cbc -e -K `echo -n "0123456789012345" | od -A n -t x1 | sed 's/ //g'` -iv '' -P
salt=0100000000000000
key=30313233343536373839303132333435
iv =00000000000000000000000000000000


別忘了,若是要用 AES-256 時,Key 要記得手動接好 XD

$  echo -n "01234567890123456789012345678901" | od -A n -t x1 | sed 's/ //g'
30313233343536373839303132333435
36373839303132333435363738393031

$ openssl aes-256-cbc -e -K 3031323334353637383930313233343536373839303132333435363738393031 -iv '' -P
salt=0100000000000000
key=3031323334353637383930313233343536373839303132333435363738393031
iv =00000000000000000000000000000000


接著,來寫一點 C++ 吧:

#include <iostream>
#include <fstream>
#include <cryptopp/aes.h>
#include <cryptopp/modes.h>
#include <cryptopp/filters.h>
// http://www.cryptopp.com/
// sudo apt-get install libcrypto++-dev
// $ g++ main.cpp -lcryptopp

int main(int argc, char **argv) {
        byte key[ CryptoPP::AES::DEFAULT_KEYLENGTH ], iv[ CryptoPP::AES::BLOCKSIZE ];
        memset( key, 0x00, CryptoPP::AES::DEFAULT_KEYLENGTH );
        memset( iv, 0x00, CryptoPP::AES::BLOCKSIZE );

        for (int i=0 ; i<CryptoPP::AES::DEFAULT_KEYLENGTH ; ++i)
                key[i] = ('0' + (i%10) ) ;

        std::cout << "block size: " << CryptoPP::AES::BLOCKSIZE << std::endl;
        std::cout << "key(" << CryptoPP::AES::DEFAULT_KEYLENGTH << "):[" << key << "]" << std::endl;

        std::string plaintext = "Hello world. To man to be a better man.";
        std::string ciphertext;

        // enc
        CryptoPP::AES::Encryption aesEncryption(key, CryptoPP::AES::DEFAULT_KEYLENGTH);
        CryptoPP::CBC_Mode_ExternalCipher::Encryption cbcEncryption( aesEncryption, iv );
        CryptoPP::StreamTransformationFilter stfEncryptor(cbcEncryption, new CryptoPP::StringSink( ciphertext ) );
        stfEncryptor.Put( reinterpret_cast<const unsigned char*>( plaintext.c_str() ), plaintext.length());
        stfEncryptor.MessageEnd();

        std::ofstream output ("/tmp/output");
        output << ciphertext ;
        output.close();

        // dec
        std::string decryptedtext;
        CryptoPP::AES::Decryption aesDecryption(key, CryptoPP::AES::DEFAULT_KEYLENGTH);
        CryptoPP::CBC_Mode_ExternalCipher::Decryption cbcDecryption( aesDecryption, iv );
        CryptoPP::StreamTransformationFilter stfDecryptor(cbcDecryption, new CryptoPP::StringSink( decryptedtext ) );
        stfDecryptor.Put( reinterpret_cast<const unsigned char*>( ciphertext.c_str() ), ciphertext.size() );
        stfDecryptor.MessageEnd();

        std::cout << "dec: [" << decryptedtext << "]" << std::endl;

        return 0;
}


執行完的產出在 /tmp/output,可以再用 openssl 把它解回來,而上述範例的 Key 長度為 16 ,因此適用於 aes-128-CBC,若想要用 aes-256-cbc 僅需把 Key 的長度調整為 32 即可搞定 :P

$ g++ t.cpp -lcryptopp
$ ./a.out
block size: 16
key(16):[0123456789012345]
dec: [Hello world. To man to be a better man.]
$ openssl aes-128-cbc -d -K `echo -n '0123456789012345' | od -A n -t x1 | sed 's/ //g'` -iv '' -in /tmp/output
Hello world. To man to be a better man.

沒有留言:

張貼留言