天天看點

C++(CrytpoPP) 與 CI(CodeIgniter PHP) 實作相同加解密算法(Crypt AES-128-CBC/HMAC SHA512),友善互動

前言

  • 如果伺服器端資料需要與其它應用程式互動,很可能需要進行加密與解密
  • 很久前用CI做的伺服器,CI自帶一個Encryption庫,預設使用使用AES-128-CBC/HMAC SHA512算法,我在VC用戶端請求伺服器資料時資料是加密的,需要實作相同算法來進行解密。

CI libraries\Encryption.php 算法簡述

加密:

$this->encryption->encrypt($plain_text)

  • 通過 HKDF 和 SHA-512 摘要算法,從你配置的 encryption_key 參數中擷取兩個密鑰:加密密鑰 和 HMAC 密鑰。
  • 生成一個随機的初始向量(IV)。
  • 使用上面的加密密鑰和 IV ,通過 AES-128 算法的 CBC 模式(或其他你配置的算法和模式)對資料進行加密。
  • 将 IV 附加到密文後。
  • 對結果進行 Base64 編碼,這樣就可以安全的儲存和傳輸它,而不用擔心字元集問題。
  • 使用 HMAC 密鑰生成一個 SHA-512 HMAC 認證消息,附加到 Base64 字元串後,以保證資料的完整性。

解密:

$this->encryption->decrypt($ciphertext)

  • 通過 HKDF 和 SHA-512 摘要算法,從你配置的 encryption_key 參數中擷取兩個密鑰:加密密鑰 和 HMAC 密鑰。 由于 encryption_key 不變,是以生成的結果和上面 encrypt() 方法生成的結果是一樣的,否則你沒辦法解密。
  • 檢查字元串的長度是否足夠長,并從字元串中分離出 HMAC ,然後驗證是否一緻(這可以防止時序攻擊), 如果驗證失敗,傳回 FALSE 。
  • 進行 Base64 解碼。
  • 從密文中分離出 IV ,并使用 IV 和 加密密鑰對資料進行解密。

源碼分析

// 原始key在配置檔案 $config['encryption_key'] = hex2bin('3b9bddd542ce5b50f425a83ab44e812b');

    // 加密部分
    public function encrypt($data, array $params = NULL)
    {
        //...參數驗證

        // hkdf函數,根據原始key, 生成加密key與散列key, 需要VC實作
        isset($params['key']) OR $params['key'] = $this->hkdf($this->_key, 'sha512', NULL, self::strlen($this->_key), 'encryption');
        // 根據配置,調用相關庫 (crypt或openssl)加密核心算法, 本文實作crypt
        if (($data = $this->{'_'.$this->_driver.'_encrypt'}($data, $params)) === FALSE)
        {
            return FALSE;
        }
        // base64轉碼
        $params['base64'] && $data = base64_encode($data);
        // 生成hmac sha512 hash
        if (isset($params['hmac_digest']))
        {
            isset($params['hmac_key']) OR $params['hmac_key'] = $this->hkdf($this->_key, 'sha512', NULL, NULL, 'authentication');
            return hash_hmac($params['hmac_digest'], $data, $params['hmac_key'], ! $params['base64']).$data;
        }

        return $data;
    }

    // Crypt加密核心算法
    protected function _mcrypt_encrypt($data, $params)
    {
        // 'handle' = mcrypt_module_open($this->_cipher, '', $this->_mode, '')
        if ( ! is_resource($params['handle']))
        {
            return FALSE;
        }

        // 生成IV
        $iv = (($iv_size = mcrypt_enc_get_iv_size($params['handle'])) > 1)
            ? $this->create_key($iv_size)
            : NULL;
        // 初始化算法
        if (mcrypt_generic_init($params['handle'], $params['key'], $iv) < 0)
        {
            if ($params['handle'] !== $this->_handle)
            {
                mcrypt_module_close($params['handle']);
            }

            return FALSE;
        }

        // CBC/EBC 原始資料長度fix
        if (in_array(strtolower(mcrypt_enc_get_modes_name($params['handle'])), array('cbc', 'ecb'), TRUE))
        {
            $block_size = mcrypt_enc_get_block_size($params['handle']);
            $pad = $block_size - (self::strlen($data) % $block_size);
            $data .= str_repeat(chr($pad), $pad);
        }
        // 生成密文,預設使用AES-128 CBC加密, 并将IV附在密文前面
        $data = (mcrypt_enc_get_modes_name($params['handle']) !== 'ECB')
            ? $iv.mcrypt_generic($params['handle'], $data)
            : mcrypt_generic($params['handle'], $data);

        mcrypt_generic_deinit($params['handle']);
        if ($params['handle'] !== $this->_handle)
        {
            mcrypt_module_close($params['handle']);
        }

        return $data;
    }

    // hkdf SHA512 雜湊演算法
    public function hkdf($key, $digest = 'sha512', $salt = NULL, $length = NULL, $info = '')
    {
        //... 參數合法性判斷,略..

        // 長度填充
        self::strlen($salt) OR $salt = str_repeat("\0", $this->_digests[$digest]);

        // 注意 加密Key的鹽為“encryption”, 散列Key的鹽為“authentication”
        $prk = hash_hmac($digest, $key, $salt, TRUE);
        $key = '';
        for ($key_block = '', $block_index = 1; self::strlen($key) < $length; $block_index++)
        {
            $key_block = hash_hmac($digest, $key_block.$info.chr($block_index), $prk, TRUE);
            $key .= $key_block;
        }

        return self::substr($key, 0, $length);
    }
    // 解密略           

C++ 實作

  • 使用CryptoPP庫,自行下載下傳編譯
  • 源檔案無其它依賴,可直接編譯

// 使用方法:

Encryption enc;

// 加密

std::string result = enc.Encrypt(text);

// 解密

std::string result = enc.Decrypt(text);

  • 源代碼
// file ci_encryption.h

#pragma once

#include <iostream>
#include <secblock.h>

using namespace CryptoPP;
using namespace std;


// 僅處理AES-128-CBC + HMAC SHA512
class Encryption{
protected:
    SecByteBlock _key;          // 儲存的二進制原始Key bin
    size_t _digest_size;    // [sha512=64位], 384=>48, 256=>32, 224=>28
public:
    Encryption();
    ~Encryption(){};

    // HKDF摘要算法,同ci的hkdf()函數
    int DeriveKeys(SecByteBlock& encrypt_key, SecByteBlock& hmac_key);

    // 與CI相同算法的加密與解密
    string Encrypt(const string& data);
    string Decrypt(const string& data);

};







// file ci_encryption.cpp

#pragma warning (disable:4996)
#include "encryption.h"

#include <hex.h>
#include <modes.h>
#include <randpool.h>
#include <hkdf.h>
#include <sha.h>
#include <base64.h>


#pragma comment (lib, "cryptlib.lib")

using namespace std;

namespace{
// 原始加密Key
static const char* __encryption_key = "3b9bddd542ce5b50f425a83ab44e812b";
};


Encryption::Encryption() : _digest_size(64){
    SSGet(StringSource(__encryption_key, true, new HexDecoder), _key);
}


// 使用hdkf 生成加密 key 和 散列(HMAC) Key
int Encryption::DeriveKeys(SecByteBlock& encrypt_key, SecByteBlock& hmac_key){
    //string key;
    //StringSource(_key, true, new HexDecoder(new StringSink(key)));
    string info("encryption");
    HKDF<SHA512> hkdf;
    encrypt_key.resize(_key.size());    // 與原始Key長度一緻 (16位)
    int ret1 = hkdf.DeriveKey(encrypt_key, encrypt_key.size(), (byte*)_key.data(), _key.size(), 0, 0, (byte*)info.data(), info.size());

    info.assign("authentication");
    hmac_key.resize(_digest_size);
    int ret2 = hkdf.DeriveKey(hmac_key, hmac_key.size(), (byte*)_key.data(), _key.size(), 0, 0, (byte*)info.data(), info.size());
    return (ret1 == encrypt_key.size() && ret2 == _digest_size);
}

string Encryption::Encrypt(const string& data){

    // 取得加密key與散列Key
    SecByteBlock encrypt_key, hmac_key;
    if (!DeriveKeys(encrypt_key, hmac_key)){
        return "";
    }

    // 加密, 生成随機IV
    SecByteBlock iv(AES::BLOCKSIZE);
    RandomPool().GenerateBlock(iv.data(), iv.size());

    // CBC AES-128 加密
    SecByteBlock cipher;
    CBC_Mode<AES>::Encryption aes(encrypt_key, encrypt_key.size(), iv); 
    //StringSource(data, true, new StreamTransformationFilter(aes, new StringSink(cipher)));
    SSGet(StringSource(data, true, new StreamTransformationFilter(aes)), cipher);

    // 合并 iv 與 cipher
    cipher = iv + cipher;

    // base64 轉碼
    string cipher_base64;
    StringSource(cipher, cipher.size(), true, new Base64Encoder(new StringSink(cipher_base64), false));

    // 生成 HMAC SHA512 摘要 (對base64進行摘要)
    SecByteBlock hash;
    HMAC<SHA512> mac(hmac_key, hmac_key.size());
    SSGet(StringSource(cipher_base64, true, new HashFilter(mac)), hash);

    // hash 轉碼 (hexEncode)
    string hash_hex_encode;
    StringSource(hash, hash.size(), true, new HexEncoder(new StringSink(hash_hex_encode)));

    // 合并hash, 密文
    return hash_hex_encode + cipher_base64;
}

string Encryption::Decrypt(const string& data){
    string result;
    // 分離出hash (hexencodieng), 原始密文 (base64encoding)
    if (data.size() < _digest_size * 2){
        return result;
    }

    // 取得Key用于解密和哈希
    SecByteBlock encrypt_key, hmac_key;
    if (!DeriveKeys(encrypt_key, hmac_key)){
        return result;
    }

    string str_hash(data, 0, _digest_size * 2); // 從0起始,取N位. 或 (data.c_str(), size)
    string str_cipher(data, str_hash.size());   // 從size起始取子串

    // 生成Hash
    SecByteBlock hash;
    HMAC<SHA512> mac(hmac_key, hmac_key.size());
    SSGet(StringSource(str_cipher, true, new HashFilter(mac)), hash);

    string hash_hex_encode; // HexEncode 字元串
    StringSource(hash, hash.size(), true, new HexEncoder(new StringSink(hash_hex_encode)));

    // 驗證Hash
    if (strcmpi(hash_hex_encode.c_str(), str_hash.c_str()) != 0){
        return result;
    }

    // 對密文進行 base64 解碼
    SecByteBlock cipher;
    SSGet(StringSource(str_cipher, true, new Base64Decoder), cipher);
    if (cipher.size() <= AES::BLOCKSIZE){
        return result;
    }

    // 分離出IV, 密文     (iv在前面)
    SecByteBlock iv(cipher, AES::BLOCKSIZE);
    SecByteBlock pure_cipher(cipher.data() + iv.size(), cipher.size() - iv.size());

    // 解密
    CBC_Mode<AES>::Decryption aes(encrypt_key, encrypt_key.size(), iv);
    StringSource(pure_cipher, pure_cipher.size(), true, new StreamTransformationFilter(aes, new StringSink(result)));
    return result;
}           

源碼下載下傳: https://download.csdn.net/download/ijiabao520/10655886

繼續閱讀