crypto-jsとPyCryptoのAES暗号(CFBモード)の互換性のための拡張について書きます。
ブロック暗号のモードは, ECB, CBC, CFB, OFBなどがあり, ECBについては暗号の危殆化により使用が非推奨になっています。
基本アイデアはこちらを参考にしました。
crypto-js
crypto-jsをインストールする。今回の例はバックエンドですが, フロントエンドでもほぼ同様です。
$ npm install crypto-js -g
CryptoJSに MODE_CFB を追加する。
var CryptoJS = require("crypto-js");
/**
* Cipher Feedback block mode.
*/
CryptoJS.mode.CFB = (function () {
var CFB = CryptoJS.lib.BlockCipherMode.extend();
CFB.Encryptor = CFB.extend({
processBlock: function (words, offset) {
// Shortcuts
var cipher = this._cipher;
var blockSize = cipher.blockSize;
generateKeystreamAndEncrypt.call(this, words, offset, blockSize, cipher);
// Remember this block to use with next block
this._prevBlock = words.slice(offset, offset + blockSize);
}
});
CFB.Decryptor = CFB.extend({
processBlock: function (words, offset) {
// Shortcuts
var cipher = this._cipher;
var blockSize = cipher.blockSize;
// Remember this block to use with next block
var thisBlock = words.slice(offset, offset + blockSize);
generateKeystreamAndEncrypt.call(this, words, offset, blockSize, cipher);
// This block becomes the previous block
this._prevBlock = thisBlock;
}
});
function generateKeystreamAndEncrypt(words, offset, blockSize, cipher) {
// Shortcut
var iv = this._iv;
// Generate keystream
if (iv) {
var keystream = iv.slice(0);
// Remove IV for subsequent blocks
this._iv = undefined;
} else {
var keystream = this._prevBlock;
}
cipher.encryptBlock(keystream, 0);
// Encrypt
for (var i = 0; i < blockSize; i++) {
words[offset + i] ^= keystream[i];
}
}
return CFB;
}());
module.exports = CryptoJS;
require()で読み込む。key と iv は hex なので ascii に変換してからAESに入れている。
'use strict';
var AES = require("crypto-js/aes");
var CryptoJS = require("./crypto-js/cfb.js")
function aesEncryptModeCFB (msg, key, iv) {
return AES.encrypt(msg, key, {iv: iv, mode: CryptoJS.mode.CFB}).toString()
}
function aesDecryptModeCFB (cipher, key, iv) {
return AES.decrypt(cipher, key, {iv: iv, mode: CryptoJS.mode.CFB}).toString(CryptoJS.enc.Utf8);
}
var encrypted = {
"key": '01ab38d5e05c92aa098921d9d4626107133c7e2ab0e4849558921ebcc242bcb0',
"iv": '6aa60df8ff95955ec605d5689036ee88',
"ciphertext": 'r19YcF8gc8bgk5NNui6I3w=='
}
var key = CryptoJS.enc.Hex.parse(encrypted.key)
var iv = CryptoJS.enc.Hex.parse(encrypted.iv)
var cipher = CryptoJS.lib.CipherParams.create({
ciphertext: CryptoJS.enc.Base64.parse(encrypted.ciphertext)
})
console.log(aesDecryptModeCFB(cipher, key, iv))
// => fisproject
console.log(aesEncryptModeCFB('fisproject', key, iv))
// => r19YcF8gc8bgk5NNui6I3w==
PyCrypto
Python側では Padding 処理を追加する。
# pip install -U pip
$ sudo pip install pycrypto
crypt-jsは Padding に PKCS#7(rfc2315)を使っているので PyCrypto で暗号・復号時にこのPadding処理を追加する。
#!/usr/bin/env python
# coding: utf-8
import os, json
import binascii
from Crypto import Random
from Crypto.Cipher import AES
# ------------------------------
# DEFINE Encryption Class
class Cryptor(object):
# AES-256 key (32 bytes)
KEY = "01ab38d5e05c92aa098921d9d4626107133c7e2ab0e4849558921ebcc242bcb0"
BLOCK_SIZE = 16
@classmethod
def _pad_string(cls, in_string):
'''Pad an input string according to PKCS#7'''
in_len = len(in_string)
pad_size = cls.BLOCK_SIZE - (in_len % cls.BLOCK_SIZE)
return in_string.ljust(in_len + pad_size, chr(pad_size))
@classmethod
def _unpad_string(cls, in_string):
'''Remove the PKCS#7 padding from a text string'''
in_len = len(in_string)
pad_size = ord(in_string[-1])
if pad_size > cls.BLOCK_SIZE:
raise ValueError('Input is not padded or padding is corrupt')
return in_string[:in_len - pad_size]
@classmethod
def generate_iv(cls, size=16):
return Random.get_random_bytes(size)
@classmethod
def encrypt(cls, in_string, in_key, in_iv=None):
key = binascii.a2b_hex(in_key)
if in_iv is None:
iv = cls.generate_iv()
in_iv = binascii.b2a_hex(iv)
else:
iv = binascii.a2b_hex(in_iv)
aes = AES.new(key, AES.MODE_CFB, iv, segment_size=128)
return in_iv, aes.encrypt(cls._pad_string(in_string))
@classmethod
def decrypt(cls, in_encrypted, in_key, in_iv):
key = binascii.a2b_hex(in_key)
iv = binascii.a2b_hex(in_iv)
aes = AES.new(key, AES.MODE_CFB, iv, segment_size=128)
decrypted = aes.decrypt(binascii.a2b_base64(in_encrypted).rstrip())
return cls._unpad_string(decrypted)
上記をimportして使う。
#!/usr/bin/env python
# coding: utf-8
from AES_Compatible_Cryptjs import Cryptor
import binascii
iv, encrypted = Cryptor.encrypt('fisproject', Cryptor.KEY)
print "iv : %s" % iv
# => iv 6aa60df8ff95955ec605d5689036ee88
print "encrypted : %s" % binascii.b2a_base64(encrypted).rstrip()
# => encrypted r19YcF8gc8bgk5NNui6I3w==
decrypted = Cryptor.decrypt('r19YcF8gc8bgk5NNui6I3w==', Cryptor.KEY, '6aa60df8ff95955ec605d5689036ee88')
print "decrypted : %s" % decrypted
# => decrypted : fisproject
codeはGitHubにあります。