Y's note

Web技術・プロダクトマネジメント・そして経営について

本ブログの更新を停止しており、今後は下記Noteに記載していきます。
https://note.com/yutakikuchi/

crypto++でのお手軽暗号

お手軽暗号

@yutakikuchi_です。
今日はcppで暗号/復号するためのエントリーを書きます。PHPPythonにはcrypt標準モジュールがありドキュメントも充実しているので簡単に暗号化できますが、cppではそれらがあまり整備されていない事もあってプログラム書く時にちょいと苦労します。尚、ここでお手軽暗号と言っているのは暗号化のブロック方式をECBで対応するためです。CBC方式でも良いのですがIVの管理も面倒だし、そもそもそこまで暗号化強度に拘らないケースを想定しています。cppでの暗号化を行う為のLibraryとしてcrypto++という様々な暗号化方式をサポートする物を利用します。
Crypto++ Library 5.6.2 - a Free C++ Class Library of Cryptographic Schemes はてなブックマーク - Crypto++ Library 5.6.2 - a Free C++ Class Library of Cryptographic Schemes

crypto++

install

以下のOneLineです。

$ sudo yum install cryptopp cryptopp-devel -y
DES × ECB

TripleDES - Crypto++ Wiki はてなブックマーク - TripleDES - Crypto++ Wiki
以下のコードでDES × ECBの暗号化/復号化ができます。crypto++のwikiにTripleDES × CBCの内容が載っていたのでそれを真似て作ってみました。暗号化の対象テキストが「魔法少女まどか☆マギカ」、暗号化keyに「Soul Gem」を指定します。ECBブロック暗号なのでIVを必要としません。処理としては単純にマルチバイト文字列を暗号化 => バイナリを16進数変換 => 16進数をバイナリに変換 => マルチバイト文字列を復元です。下に実行結果も載せておきます。

#include <iostream>
#include "modes.h"
#include "filters.h"
#include "hex.h"
#include "des.h"

using namespace std;
using namespace CryptoPP;

int main(int argc, char* argv[]) {

	string plain = "魔法少女まどか☆マギカ";
	string cipher, encoded, decoded, recovered;
	ECB_Mode<DES>::Encryption encctx;
        byte key[DES::DEFAULT_KEYLENGTH];
	memcpy( key, "Soul Gem", DES::DEFAULT_KEYLENGTH );
	encctx.SetKey( key, DES::DEFAULT_KEYLENGTH );

	// ciper
	StringSource( plain, true, 
        new StreamTransformationFilter( encctx,
            new StringSink( cipher )
			//StreamTransformationFilter::PKCS_PADDING
                       //StreamTransformationFilter::ZEROS_PADDING
        )      
        ); 
	//cout << "cipher text: " << cipher << endl;	

	// hex encode
	StringSource( cipher, true,
    	new HexEncoder( 
        	new StringSink( encoded )
    	)
	);
	cout << "encode text: " << encoded << endl;	
	
        // hex decode
	StringSource( encoded, true,
    	new HexDecoder( 
        	new StringSink( decoded )
    	)
	);
	// cout << "decode text: " <<  decoded << endl;	

	// ciper decode
	ECB_Mode<DES>::Decryption decctx;
	decctx.SetKey( key, DES::DEFAULT_KEYLENGTH );
	StringSource( decoded, true, 
        new StreamTransformationFilter( decctx,
            new StringSink( recovered )
			//StreamTransformationFilter::PKCS_PADDING
			//StreamTransformationFilter::ZEROS_PADDING
        )
        );
	cout << "recovered text: " << recovered << endl;
	return 0;
}
$ g++ -I/usr/include/cryptopp -lcryptopp crypto.cpp
$ ./a.out                                  
encode text: B374EB12891FFF5C538281C88A612D522F2CE61360BA5B6776EB2A32E77E5C66B42978BA11B1C5E3
recovered text: 魔法少女まどか☆マギカ
Support Padding

Crypto++: StreamTransformationFilter Class Reference はてなブックマーク - Crypto++: StreamTransformationFilter Class Reference

enum  BlockPaddingScheme { 
  NO_PADDING, 
  ZEROS_PADDING, 
  PKCS_PADDING, 
  ONE_AND_ZEROS_PADDING, 
  DEFAULT_PADDING 
}

crypto++では暗号化ブロックに分割した時のPaddingとして上の内容をサポートしています。Paddingは特に異なる言語間で暗号化/復号化する時に問題になる事が多いです。例としてはPHPですが、PHPmcrypt関数では自動的にZEROS_PADDING(NULL_PADDING)となってしまいます。もし異なる言語間で連携をする時に平文と暗号化keyは一致しているんだけど、暗号文が異なってしまったという場合はPaddingの指定ミスの可能性があると覚えておけば嵌ることは無いと思います。次はPHPでencryptした結果が上のcrypto++とは異なっている事を表す例です。encode textの部分ですね。

<?php

function pkcs5_pad ($text, $blocksize) 
{ 
    $pad = $blocksize - (strlen($text) % $blocksize); 
    return $text . str_repeat(chr($pad), $pad); 
}

function pkcs5_unpad($text) 
{ 
    $pad = ord($text{strlen($text)-1}); 
    if ($pad > strlen($text)) return false; 
    if (strspn($text, chr($pad), strlen($text) - $pad) != $pad) return false; 
    return substr($text, 0, -1 * $pad); 
}  

//$size = mcrypt_get_block_size(MCRYPT_DES, MCRYPT_MODE_ECB);
$message = '魔法少女まどか☆マギカ';
//$message = pkcs5_pad( $message, $size );
$key = 'Soul Gem';

$encryptedMessage = mcrypt_encrypt(MCRYPT_DES, $key, $message, MCRYPT_MODE_ECB);
$decryptedMessage = mcrypt_decrypt(MCRYPT_DES, $key, $encryptedMessage, MCRYPT_MODE_ECB);

echo "encode text:" . strtoupper(bin2hex($encryptedMessage)) . "\n";
echo "recovered text:$decryptedMessage";
// phpでmcryptを使う為の設定
$ wget http://dl.fedoraproject.org/pub/epel/6/x86_64/epel-release-6-8.noarch.rpm
$ wget http://ftp.riken.jp/Linux/fedora/epel/RPM-GPG-KEY-EPEL-6
$ sudo rpm --import RPM-GPG-KEY-EPEL-6
$ sudo rpm -i epel-release-6-8.noarch.rpm
$ sudo yum install php-mcrypt -y

// 上で書いたプログラムを実行
$ php crypto.php 
encode text:B374EB12891FFF5C538281C88A612D522F2CE61360BA5B6776EB2A32E77E5C6656FAC9AFC18DA65F
recovered text:魔法少女まどか☆マギカ

ではcrypto++とPHPを連携する時にはどうすれば良いか?一般的には「crypto++でZERO_PADDINGを指定する」、「PHPでPKCS5Paddingを自前で実装する」のどちらかで対応すると思います。暗号化ブロックの観点から言うとPKCS5でやるのが一般的かもしれません。PKCS5Paddingは上のPHPmcrypt_get_block_size、pkcs5_padをコメントインするだけです。pkcs5_pad関数は以下のURLから持ってきました。PHP: Mcrypt Functions - Manual はてなブックマーク - PHP: Mcrypt Functions - Manual
当然crypto++のコードも修正が必要で、StreamTransformationFilter::PKCS_PADDING,StreamTransformationFilter::ZEROS_PADDINGを内容に合せてコメントインする必要があります。下はZEROS_PADDINGを指定した実行例です。今度はencode textの部分がcrypto++とPHPで一致していますね!

$ cp crypto.cpp crypto2.cpp
$ vi crypto.cpp // StreamTransformationFilter::ZEROS_PADDINGをコメントイン
$ diff -u crypto.cpp crypto2.cpp 
--- crypto.cpp	2013-11-15 03:00:46.778536984 +0900
+++ crypto2.cpp	2013-11-15 03:00:02.604531919 +0900
@@ -19,8 +19,9 @@
 	// ciper
 	StringSource( plain, true, 
         new StreamTransformationFilter( encctx,
-            new StringSink( cipher ),
-            StreamTransformationFilter::ZEROS_PADDING
+            new StringSink( cipher )
+			//StreamTransformationFilter::PKCS_PADDING
+      //StreamTransformationFilter::ZEROS_PADDING
         )      
     ); 
 	cout << "cipher text: " << cipher << endl;	
@@ -46,8 +47,9 @@
 	decctx.SetKey( key, DES::DEFAULT_KEYLENGTH );
 	StringSource( decoded, true, 
         new StreamTransformationFilter( decctx,
-            new StringSink( recovered ),
-			      StreamTransformationFilter::ZEROS_PADDING
+            new StringSink( recovered )
+			//StreamTransformationFilter::PKCS_PADDING
+			//StreamTransformationFilter::ZEROS_PADDING
         )
     );
 	cout << "recovered text: " << recovered << endl;

$ g++ -I/usr/include/cryptopp -lcryptopp crypto.cpp
$ ./a.out
encode text: B374EB12891FFF5C538281C88A612D522F2CE61360BA5B6776EB2A32E77E5C6656FAC9AFC18DA65F
recovered text: 魔法少女まどか☆マギカ