Swiftで文字列をAESで暗号化したい。
のですが、Security FrameworkはObjective-C向けのAPIしか提供していません。
しかもC インタフェースなのか…
ということで、Swiftで利用可能な暗号化ライブラリを調べていたところ、CryptoSwiftが見つかったので利用してみます。
READMEのWhyの項に、'Why? Because I can.' と書かれていてかなりCoolです。
— てんてん (@tenten0213) February 9, 2017
機能はREADMEのFeaturesに載っていて、Hash、CRC、AES、Blowfish等の暗号化、HMAC、PBKDFなどが提供されています。
一通り使いそうな機能が揃っているので、良さそうです。
試してみる
READMEのAll at once通りにやってみると以下のようになります。
AESのイニシャライザにkey
とiv
を設定します。
iv
(初期化ベクトル)はオプショナルで、Blockmode(暗号利用モード)は省略するとCBCが、Paddingは省略するとPKCS7がデフォルトで選ばれます。
encryptの結果はバイト配列(Array<UInt8>
)で返却されます。HexStringにして比較しています。
do {
let aes = try AES(key: "passwordpassword", iv: "drowssapdrowssap") // aes128
// let aes = try AES(key: "passwordpassword", iv: "drowssapdrowssap", blockMode: .CBC, padding: PKCS7()) と同等
let ciphertext = try aes.encrypt(Array("Nullam quis risus eget urna mollis ornare vel eu leo.".utf8))
XCTAssertEqual(ciphertext.toHexString(), "29951512f09c87d81e4ce8640f7cf8e348d4120f3df1807cfee22eadea19bb66ecfaba40259ffb5a6aa1f724f92115a39bcb63c0f519faec5a4275415e962daf")
} catch { }
AES-256で暗号化するには
AES-256で暗号化するということは256ビット長の暗号鍵を用いて暗号化する
ということなので、256ビット(32バイト)の暗号鍵を引数で渡します。
CBCではブロックサイズと同サイズの初期化ベクトルが必要です。
AESでは128ビット(16バイト)のブロックサイズを使用するので、128ビット(16バイト)の初期化ベクトルを引数で渡します。
do {
let aes = try AES(key: "passwordpasswordpasswordpassword", iv: "drowssapdrowssap")
let ciphertext = try aes.encrypt(Array("Nullam quis risus eget urna mollis ornare vel eu leo.".utf8))
XCTAssertEqual(ciphertext.toHexString(), "5105c187e62c6f2c07ce9c0d5966ae01d2221f8a736e89f52dcc7fbf6d32dfd1b5db8b0a4e468dea273678b4be255a50811bfe14b6cf3a3a9116baac77d3bcfc")
} catch { }
暗号化モードECBで行う場合
こんな感じです。
ECBは初期化ベクトルを必要としないため、インスタンス化しただけのStringを渡しています。
do {
let aes = try AES(key: "passwordpasswordpasswordpassword", iv: String(), blockMode: .ECB, padding: PKCS7())
let ciphertext = try aes.encrypt(Array("Nullam quis risus eget urna mollis ornare vel eu leo.".utf8))
XCTAssertEqual(ciphertext.toHexString(), "7693ce6d6a8a0dd4626aca32441bbede21e287c71997b85cf71e59bb3997a2a7623c49eecf345b2511c113326712f33526d70110c756fd8d2550388afba53da5")
} catch { }
ECBモードは脆弱であるため、暗号化プロトコルとしての使用は推奨されません。採用する際は注意してください。
ECBモードの欠点は、同じ鍵を用いた場合ある平文ブロックを暗号化した結果の暗号文ブロックが常に同じとなることである。
このため、データのパターンを隠蔽することができない。
メッセージの機密性の保持には向かず、暗号化プロトコルにおける使用は推奨されない。
同じ入力に対して常に同じ出力を返すことから、ECBモードは反射攻撃に対しても脆弱である。
PKCS#5とか#7のはなし
PKCS#7と類似したパディング方式にPKCS#5というものがあります。
PKCS#5はPKCS#7のサブセットで、PKCS#7は1〜255バイトの可変長ブロックに対応しているのに対してPKCS#5は8バイトのブロックにしか対応していません。
AESは16バイトのブロックサイズを使用するので、8バイトのブロックしか対応していないPKCS#5は、厳密にはAESで利用できません。
担当しているプロジェクトのサーバサイドはJavaで実装されているのですが、Javaの暗号化アーキテクチャ(JCA)にはPKCS#7を指定できるような定義はありません。
Javaプラットフォームの実装は、すべて次の標準のCipher変換とカッコ内の鍵サイズをサポートする必要があります。
AES/CBC/NoPadding (128)
AES/CBC/PKCS5Padding (128)
AES/ECB/NoPadding (128)
AES/ECB/PKCS5Padding (128)
DES/CBC/NoPadding (56)
DES/CBC/PKCS5Padding (56)
DES/ECB/NoPadding (56)
DES/ECB/PKCS5Padding (56)
DESede/CBC/NoPadding (168)
DESede/CBC/PKCS5Padding (168)
DESede/ECB/NoPadding (168)
DESede/ECB/PKCS5Padding (168)
RSA/ECB/PKCS1Padding (1024、2048)
RSA/ECB/OAEPWithSHA-1AndMGF1Padding (1024、2048)
RSA/ECB/OAEPWithSHA-256AndMGF1Padding (1024、2048)
これらの変換については、Java暗号化アーキテクチャ標準アルゴリズム名のドキュメントのCipherのセクションで説明されています。サポートされているその他の変換については、実装のリリース・ドキュメントを
ただ、上記定義の通りAESのサポートをしており、PKCS#5のパディングを指定することが可能です。
実際はPKCS#7の処理が行われるとのことなので、上でCryptoSwiftを利用してAES-128で暗号化した処理(一番最初の例)と同様の処理をJavaで実装してみました。
Cipher.getInstance("AES/CBC/PKCS5Padding")
とPKCS#5を指定していますが、同様の値が返却されていることが確認できます。
Key key = new SecretKeySpec("passwordpassword".getBytes(), "AES");
IvParameterSpec ips = new IvParameterSpec("drowssapdrowssap".getBytes());
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, key, ips);
byte[] enc = cipher.doFinal("Nullam quis risus eget urna mollis ornare vel eu leo.".getBytes());
String actual = new String(Hex.encodeHex(enc)); // org.apache.commons.codec.binary.Hex
assertThat(actual, is("29951512f09c87d81e4ce8640f7cf8e348d4120f3df1807cfee22eadea19bb66ecfaba40259ffb5a6aa1f724f92115a39bcb63c0f519faec5a4275415e962daf"));
サーバはPKCS#5で、CryptoSwiftはPKCS#7しか対応していない!どうしよう!ってちょっと焦りましたが、どうやら問題無さそうです。