goのcfbとmcryptのcfb
mcryptで書かれた3DES+CFBのEncrypt/Decryptをgoで書き直すことになったので、ハマったところのメモ。
そもそも3DES(DES)とは
DESを3回(暗号化->復号->暗号化)をやって強度を高めたもの。 DESは下記がわかりやすい。
CFB(Cipher FeedBack)モード
DESはブロック暗号方式なのでブロックごとに暗号化する。 ただ、普通に暗号するだけでは総当たり攻撃などでKeyが推測されるリスクがあるため、暗号化時に前後のブロックを工夫して使う。 その方法(モード)の一つにCFB(Cipher FeedBack)がある。
CFBモードは平文ブロックを直接暗号化するのではなく、その前のブロックの暗号化ブロックをさらに暗号化し、その結果と平文のxorをとる。 最初のブロックに対しては初期化ブロックを暗号化する。
ハマったところ
このCFBにはsegmentという概念(DESのブロックサイズとは別)がある。 segmentが何かと言うと、xorを取る暗号化ブロックの一部(segument size)だけを利用する、というもの。 詳しくはNIST Special Publication 800-38aに書いてある(これを理解することは不可能だろう)。
segment sizeが8bitなら、8bitごとにxorをとっていく。そして平文の最初の8bitを暗号化するために暗号化ブロックの最初の8bitを使う。 そして次のブロックの暗号化を行う際、再び暗号化を行った結果を使ってxorを取る。
これをgoで書くとこんな感じ。
func (x *cfb) XORKeyStream(dst, src []byte) { for i := range src { x.b.Encrypt(x.out, x.in) copy(x.in[:x.blockSize-1], x.in[1:]) if x.decrypt { x.in[x.blockSize-1] = src[i] } dst[i] = src[i]^x.out[0] if !x.decrypt { x.in[x.blockSize-1] = dst[i] } } }
mcryptの場合、CFBのsegment sizeは8bit。
CFB: The Cipher-Feedback Mode (in 8bit). This is a self-synchronizing stream cipher implemented from a block cipher.
copyで暗号化ブロックから1byte分を使って、1byteの平文だけxorをとっている。 それを平文全体に対して繰り返す。
それに対して、goの標準ライブラリに含まれるCFBの実装は以下のようになっている。
func (x *cfb) XORKeyStream(dst, src []byte) { if len(dst) < len(src) { panic("crypto/cipher: output smaller than input") } if subtle.InexactOverlap(dst[:len(src)], src) { panic("crypto/cipher: invalid buffer overlap") } for len(src) > 0 { if x.outUsed == len(x.out) { x.b.Encrypt(x.out, x.next) x.outUsed = 0 } if x.decrypt { // We can precompute a larger segment of the // keystream on decryption. This will allow // larger batches for xor, and we should be // able to match CTR/OFB performance. copy(x.next[x.outUsed:], src) } n := xorBytes(dst, src, x.out[x.outUsed:]) if !x.decrypt { copy(x.next[x.outUsed:], dst) } dst = dst[n:] src = src[n:] x.outUsed += n } }
基本的にoutUsedは暗号化のブロックサイズが入る。今回の環境ではブロックサイズは8byteだったので、8byte単位に暗号化&xorを行っていたため、Encrypt/Decryptがうまくいかないという状態だった。
最後に
暗号化は詳しくないので間違ってる可能性あります。 アリスの国に行かねば。