對稱加密算法的分組模式及其Go語言實現

  之前介紹的DES、3DES、AES加密算法,只能加密固定長度的明文。如果需要加密任意長度的明文,需要對明文分組加密。DES、3DES、AES等又稱分組密碼,而分組有很多模式,如:ECB模式、CBC模式、CFB模式、OFB模式、CTR模式,如下將逐一介紹。
 

ECB模式

  ECB模式,全稱Electronic Codebook模式,譯爲電子密碼本模式,即用相同的密碼分別對明文分組獨立加密。ECB模式是最簡單的模式,因爲相同的明文分組會加密爲相同的密文分組,因此存在一定風險。
 
  如下爲ECB模式示意圖:
 
對稱加密算法的分組模式及其Go語言實現
 
  另外當最後一個明文分組的內容,小於分組長度時,需要用特定的數據進行填充。
 

CBC模式

 
  CBC模式,全稱Cipher Block Chaining模式,譯爲密文分組鏈接模式,即加密算法的輸入是上一個密文分組和下一個明文分組的異或。因爲是將上一個密文分組和下一個明文分組的內容混合加密,因此可以避免ECB模式的缺陷。當加密第一個明文分組時,由於不存在上一個密文分組,因此需要準備與分組等長的初始化向量IV,來代替上一個密文分組。
 
  如下爲CBC模式示意圖:
 
對稱加密算法的分組模式及其Go語言實現
 
  go標準庫中CBC模式代碼如下:

type cbc struct {
    //b爲加密算法,如DES、AES
    b         Block
    //加密算法支持的明文分組長度
    blockSize int
    //初始化向量IV
    iv        []byte
    //臨時變量
    tmp       []byte
}

type cbcEncrypter cbc

//指定加密算法和IV
func NewCBCEncrypter(b Block, iv []byte) BlockMode {
    if len(iv) != b.BlockSize() {
        panic("cipher.NewCBCEncrypter: IV length must equal block size")
    }
    if cbc, ok := b.(cbcEncAble); ok {
        return cbc.NewCBCEncrypter(iv)
    }
    return (*cbcEncrypter)(newCBC(b, iv))
}

//加密
func (x *cbcEncrypter) CryptBlocks(dst, src []byte) {
    if len(src)%x.blockSize != 0 {
        panic("crypto/cipher: input not full blocks")
    }
    if len(dst) < len(src) {
        panic("crypto/cipher: output smaller than input")
    }

    iv := x.iv

    for len(src) > 0 {
        //上一個密文分組和下一個明文分組的異或
        //當加密第一個明文分組時,使用初始化向量IV
        xorBytes(dst[:x.blockSize], src[:x.blockSize], iv)
        //執行加密算法
        x.b.Encrypt(dst[:x.blockSize], dst[:x.blockSize])

        iv = dst[:x.blockSize]
        src = src[x.blockSize:]
        dst = dst[x.blockSize:]
    }

    copy(x.iv, iv)
}
//代碼位置src/crypto/cipher/cbc.go

CFB模式

 
  CFB模式,全稱Cipher FeedBack模式,譯爲密文反饋模式,即上一個密文分組作爲加密算法的輸入,輸出與明文異或作爲下一個分組的密文。在CFB模式中,明文分組和密文分組之間只有一次異或。
 
  如下爲CFB模式示意圖:
 
對稱加密算法的分組模式及其Go語言實現
 
  CFB模式與一次性密碼本相似,都是通過將明文與隨機比特序列進行異或運算來生成密文。但由於CFB模式中密碼算法的輸出是通過計算得到的,並非真正的隨機數,因此不具備一次性密碼本那樣理論上不可破譯的性質。CFB模式可以看做使用分組方式實現流密碼的方式。
 
  go標準庫中CFB模式代碼如下:

type cfb struct {
    //加密算法
    b       Block
    //加密的輸入
    next    []byte
    //加密的輸出
    out     []byte
    outUsed int

    decrypt bool
}

//加密或解密
//decrypt爲true表示解密
func (x *cfb) XORKeyStream(dst, src []byte) {
    for len(src) > 0 {
        if x.outUsed == len(x.out) {
            x.b.Encrypt(x.out, x.next)
            x.outUsed = 0
        }

        if x.decrypt {
            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
    }
}

//加密器
func NewCFBEncrypter(block Block, iv []byte) Stream {
    return newCFB(block, iv, false)
}

//解密器
func NewCFBDecrypter(block Block, iv []byte) Stream {
    return newCFB(block, iv, true)
}

func newCFB(block Block, iv []byte, decrypt bool) Stream {
    //分組長度
    blockSize := block.BlockSize()
    if len(iv) != blockSize {
        //初始化向量要求與分組長度等長
        panic("cipher.newCFB: IV length must equal block size")
    }
    x := &cfb{
        b:       block,
        out:     make([]byte, blockSize),
        next:    make([]byte, blockSize),
        outUsed: blockSize,
        decrypt: decrypt,
    }
    //加密的輸入
    copy(x.next, iv)

    return x
}
//代碼位置src/crypto/cipher/cfb.go

OFB模式

 
  OFB模式,全稱Output Feedback模式,譯爲輸出反饋模式。OFB模式與CFB模式類似,只是加密算法的輸入是上一次加密的輸出。在OFB模式中,異或所需的密鑰流,可以事先通過密碼算法生成,即生成密鑰流的操作可以與異或運算並行。
 
  OFB模式加密和處理解密邏輯相同,明文與密鑰流異或生成密文,密文與密鑰流異或生成明文。
 
  如下爲OFB模式示意圖:
 
對稱加密算法的分組模式及其Go語言實現
 
  go標準庫中OFB模式代碼如下:

type ofb struct {
    //加密算法
    b       Block
    //加密的輸入
    cipher  []byte
    out     []byte
    outUsed int
}

func NewOFB(b Block, iv []byte) Stream {
    //分組長度
    blockSize := b.BlockSize()
    if len(iv) != blockSize {
        return nil
    }
    //const streamBufferSize = 512
    bufSize := streamBufferSize
    if bufSize < blockSize {
        bufSize = blockSize
    }
    x := &ofb{
        b:       b,
        cipher:  make([]byte, blockSize),
        out:     make([]byte, 0, bufSize),
        outUsed: 0,
    }

    //加密的輸入
    copy(x.cipher, iv)
    return x
}

//生成密鑰流
func (x *ofb) refill() {
    bs := x.b.BlockSize()
    remain := len(x.out) - x.outUsed
    if remain > x.outUsed {
        return
    }
    copy(x.out, x.out[x.outUsed:])
    x.out = x.out[:cap(x.out)]
    for remain < len(x.out)-bs {
        x.b.Encrypt(x.cipher, x.cipher)
        copy(x.out[remain:], x.cipher)
        remain += bs
    }
    x.out = x.out[:remain]
    x.outUsed = 0
}

func (x *ofb) XORKeyStream(dst, src []byte) {
    for len(src) > 0 {
        if x.outUsed >= len(x.out)-x.b.BlockSize() {
            //生成密鑰流
            x.refill()
        }
        //與密鑰流異或運算
        n := xorBytes(dst, src, x.out[x.outUsed:])
        dst = dst[n:]
        src = src[n:]
        x.outUsed += n
    }
}
//代碼位置src/crypto/cipher/ofb.go

CTR模式

 
  CTR模式,全稱Counter模式,譯爲計數器模式。CTR模式中,每個分組對應一個逐次累加的計數器,並通過對計數器進行加密來生成密鑰流。也即最終的密文分組是通過將計數器加密得到的比特序列,與明文分組進行異或運算得到的。
 
  如下爲CTR模式示意圖:
 
對稱加密算法的分組模式及其Go語言實現
 
  go標準庫中CTR模式代碼如下:

type ctr struct {
    //加密算法
    b       Block
    //加密的輸入
    ctr     []byte
    out     []byte
    outUsed int
}

const streamBufferSize = 512

type ctrAble interface {
    NewCTR(iv []byte) Stream
}

func NewCTR(block Block, iv []byte) Stream {
    if ctr, ok := block.(ctrAble); ok {
        return ctr.NewCTR(iv)
    }
    if len(iv) != block.BlockSize() {
        panic("cipher.NewCTR: IV length must equal block size")
    }
    bufSize := streamBufferSize
    if bufSize < block.BlockSize() {
        bufSize = block.BlockSize()
    }
    return &ctr{
        b:       block,
        ctr:     dup(iv),
        out:     make([]byte, 0, bufSize),
        outUsed: 0,
    }
}

//生成密鑰流
func (x *ctr) refill() {
    remain := len(x.out) - x.outUsed
    copy(x.out, x.out[x.outUsed:])
    x.out = x.out[:cap(x.out)]
    bs := x.b.BlockSize()
    for remain <= len(x.out)-bs {
        x.b.Encrypt(x.out[remain:], x.ctr)
        remain += bs

        //計數器遞增
        for i := len(x.ctr) - 1; i >= 0; i-- {
            x.ctr[i]++
            if x.ctr[i] != 0 {
                break
            }
        }
    }
    x.out = x.out[:remain]
    x.outUsed = 0
}

func (x *ctr) XORKeyStream(dst, src []byte) {
    for len(src) > 0 {
        if x.outUsed >= len(x.out)-x.b.BlockSize() {
            //生成密鑰流
            x.refill()
        }
        //與密鑰流異或運算
        n := xorBytes(dst, src, x.out[x.outUsed:])
        dst = dst[n:]
        src = src[n:]
        x.outUsed += n
    }
}

Fabric中CBC模式的AES加密實現

 
  代碼如下:

//AES加密、CBC模式、PKCS7填充算法
func AESCBCPKCS7Encrypt(key, src []byte) ([]byte, error) {
    //PKCS7填充算法
    tmp := pkcs7Padding(src)
    //AES加密、CBC模式
    return aesCBCEncrypt(key, tmp)
}

//PKCS7填充算法
//PKCS7即填充字符串由一個字節序列組成,每個字節填充該字節序列的長度
func pkcs7Padding(src []byte) []byte {
    padding := aes.BlockSize - len(src)%aes.BlockSize
    padtext := bytes.Repeat([]byte{byte(padding)}, padding)
    return append(src, padtext...)
}

//AES加密、CBC模式
func aesCBCEncrypt(key, s []byte) ([]byte, error) {
    if len(s)%aes.BlockSize != 0 {
        return nil, errors.New("Invalid plaintext. It must be a multiple of the block size")
    }

    block, err := aes.NewCipher(key)
    if err != nil {
        return nil, err
    }

    ciphertext := make([]byte, aes.BlockSize+len(s))
    //初始向量IV
    iv := ciphertext[:aes.BlockSize]
    if _, err := io.ReadFull(rand.Reader, iv); err != nil {
        return nil, err
    }

    mode := cipher.NewCBCEncrypter(block, iv)
    mode.CryptBlocks(ciphertext[aes.BlockSize:], s)

    return ciphertext, nil
}
//代碼位置github.com/hyperledger/fabric/bccsp/sw/aes.go

後記

 
  ECB模式因其高風險,不應再使用。CBC模式、CFB模式、OFB模式、CTR模式,均可使用。其中Fabric中使用了CBC模式。
  待續。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章