マイ備忘録

あくまで個人の意見、メモです。

goのcfbとmcryptのcfb

mcryptで書かれた3DES+CFBのEncrypt/Decryptをgoで書き直すことになったので、ハマったところのメモ。

そもそも3DES(DES)とは

DESを3回(暗号化->復号->暗号化)をやって強度を高めたもの。 DESは下記がわかりやすい。

www.atmarkit.co.jp

CFB(Cipher FeedBack)モード

DESはブロック暗号方式なのでブロックごとに暗号化する。 ただ、普通に暗号するだけでは総当たり攻撃などでKeyが推測されるリスクがあるため、暗号化時に前後のブロックを工夫して使う。 その方法(モード)の一つにCFB(Cipher FeedBack)がある。

CFBモードは平文ブロックを直接暗号化するのではなく、その前のブロックの暗号化ブロックをさらに暗号化し、その結果と平文のxorをとる。 最初のブロックに対しては初期化ブロックを暗号化する。

wikipediaより https://upload.wikimedia.org/wikipedia/commons/9/9d/CFB_encryption.svg

ハマったところ

この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.

linux.die.net

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
    }
}

golang.org

基本的にoutUsedは暗号化のブロックサイズが入る。今回の環境ではブロックサイズは8byteだったので、8byte単位に暗号化&xorを行っていたため、Encrypt/Decryptがうまくいかないという状態だった。

最後に

暗号化は詳しくないので間違ってる可能性あります。 アリスの国に行かねば。