DES、3DES加密算法原理及其GO語言實現

  DES加密算法,爲對稱加密算法中的一種。70年代初由IBM研發,後1977年被美國國家標準局採納爲數據加密標準,即DES全稱的由來:Data Encryption Standard。對稱加密算法,是相對於非對稱加密算法而言的。兩者區別在於,對稱加密在加密和解密時使用同一密鑰,而非對稱加密在加密和解密時使用不同的密鑰,即公鑰和私鑰。常見的DES、3DES、AES均爲對稱加密算法,而RSA、橢圓曲線加密算法,均爲非對稱加密算法。

  DES是以64比特的明文爲一個單位來進行加密的,超過64比特的數據,要求按固定的64比特的大小分組,分組有很多模式,後續單獨總結,暫時先介紹DES加密算法。DES使用的密鑰長度爲64比特,但由於每隔7個比特設置一個奇偶校驗位,因此其密鑰長度實際爲56比特。奇偶校驗爲最簡單的錯誤檢測碼,即根據一組二進制代碼中1的個數是奇數或偶數來檢測錯誤。

Feistel網絡

  DES的基本結構,由IBM公司的Horst Feistel設計,因此稱Feistel網絡。在Feistel網絡中,加密的每個步驟稱爲輪,經過初始置換後的64位明文,進行了16輪Feistel輪的加密過程,最後經過終結置換後形成最終的64位密文。如下爲Feistel網絡的示意圖:
DES、3DES加密算法原理及其GO語言實現

  64比特明文被分爲左、右兩部分處理,右側數據和子密鑰經過輪函數f生成用於加密左側數據的比特序列,與左側數據異或運算,運算結果輸出爲加密後的左側,右側數據則直接輸出爲右側。
  其中子密鑰爲本輪加密使用的密鑰,每次Feistel均使用不同的子密鑰。子密鑰的計算,以及輪函數的細節,稍後下文介紹。由於一次Feistel輪並不會加密右側,因此需要將上一輪輸出後的左右兩側對調後,重複Feistel輪的過程,DES算法共計進行16次Feistel輪,最後一輪輸出後左右兩側無需對調。

  DES加密和解密的過程一致,均使用Feistel網絡實現,區別僅在於解密時,密文作爲輸入,並逆序使用子密鑰。
  go標準庫中DES算法實現如下:

func cryptBlock(subkeys []uint64, dst, src []byte, decrypt bool) {
    b := binary.BigEndian.Uint64(src)
    //初始置換
    b = permuteInitialBlock(b)
    left, right := uint32(b>>32), uint32(b)

    var subkey uint64
    //共計16次feistel輪
    for i := 0; i < 16; i++ {
        //加密和解密使用子密鑰順序相反
        if decrypt {
            subkey = subkeys[15-i]
        } else {
            subkey = subkeys[i]
        }
        //feistel輪函數
        left, right = right, left^feistel(right, subkey)
    }
    //最後一輪無需對調
    preOutput := (uint64(right) << 32) | uint64(left)
    //終結置換
    binary.BigEndian.PutUint64(dst, permuteFinalBlock(preOutput))
}
//代碼位置src/crypto/des/block.go

初始置換和終結置換

  進入Feistel輪之前,64位明文需做一次初始置換。Feistel輪結束後,需做一次反向操作,即終結置換。
  附go標準庫中使用的初始置換表和終結置換表如下:

//初始置換表
var initialPermutation = [64]byte{
    6, 14, 22, 30, 38, 46, 54, 62,
    4, 12, 20, 28, 36, 44, 52, 60,
    2, 10, 18, 26, 34, 42, 50, 58,
    0, 8, 16, 24, 32, 40, 48, 56,
    7, 15, 23, 31, 39, 47, 55, 63,
    5, 13, 21, 29, 37, 45, 53, 61,
    3, 11, 19, 27, 35, 43, 51, 59,
    1, 9, 17, 25, 33, 41, 49, 57,
}

//終結置換表
var finalPermutation = [64]byte{
    24, 56, 16, 48, 8, 40, 0, 32,
    25, 57, 17, 49, 9, 41, 1, 33,
    26, 58, 18, 50, 10, 42, 2, 34,
    27, 59, 19, 51, 11, 43, 3, 35,
    28, 60, 20, 52, 12, 44, 4, 36,
    29, 61, 21, 53, 13, 45, 5, 37,
    30, 62, 22, 54, 14, 46, 6, 38,
    31, 63, 23, 55, 15, 47, 7, 39,
}
//代碼位置src/crypto/des/const.go

子密鑰的計算

  DES初始密鑰爲64位,其中8位用於奇偶校驗,實際密鑰爲56位,64位初始密鑰經過PC-1密鑰置換後,生成56位串。經PC-1置換後56位的串,分爲左右兩部分,各28位,分別左移1位,形成C0和D0,C0和D0合併成56位,經PC-2置換後生成48位子密鑰K0。C0和D0分別左移1位,形成C1和D1,C1和D1合併成56位,經PC-2置換後生成子密鑰K1。以此類推,直至生成子密鑰K15。但注意每輪循環左移的位數,有如下規定:

var ksRotations = [16]uint8{1, 1, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 1}
//代碼位置src/crypto/des/const.go

  如下爲子密鑰計算示意圖:
DES、3DES加密算法原理及其GO語言實現

  go標準庫中DES子密鑰計算的代碼如下:

func (c *desCipher) generateSubkeys(keyBytes []byte) {
    key := binary.BigEndian.Uint64(keyBytes)
    //PC-1密鑰置換,生成56位串
    permutedKey := permuteBlock(key, permutedChoice1[:])

    //56位串分左右兩部分,各28位,ksRotate爲依次循環左移1位
    leftRotations := ksRotate(uint32(permutedKey >> 28))
    rightRotations := ksRotate(uint32(permutedKey<<4) >> 4)

    //生成子密鑰
    for i := 0; i < 16; i++ {
        //合併左右兩部分,之後PC-2置換
        pc2Input := uint64(leftRotations[i])<<28 | uint64(rightRotations[i])
        c.subkeys[i] = permuteBlock(pc2Input, permutedChoice2[:])
    }
}
//代碼位置src/crypto/des/block.go

  附go標準庫中使用的PC-1置換表和PC-2置換表:

//PC-1置換表
var permutedChoice1 = [56]byte{
    7, 15, 23, 31, 39, 47, 55, 63,
    6, 14, 22, 30, 38, 46, 54, 62,
    5, 13, 21, 29, 37, 45, 53, 61,
    4, 12, 20, 28, 1, 9, 17, 25,
    33, 41, 49, 57, 2, 10, 18, 26,
    34, 42, 50, 58, 3, 11, 19, 27,
    35, 43, 51, 59, 36, 44, 52, 60,
}

//PC-2置換表
var permutedChoice2 = [48]byte{
    42, 39, 45, 32, 55, 51, 53, 28,
    41, 50, 35, 46, 33, 37, 44, 52,
    30, 48, 40, 49, 29, 36, 43, 54,
    15, 4, 25, 19, 9, 1, 26, 16,
    5, 11, 23, 8, 12, 7, 17, 0,
    22, 3, 10, 14, 6, 20, 27, 24,
}
//代碼位置src/crypto/des/const.go

Feistel輪函數

  每次Feistel輪函數內部,均經過4種運算,即:
  1、擴展置換:右側32位做擴展置換,擴展置換將32位輸入擴展成爲48位輸出,使得擴展後輸出數據長度與48位子密鑰等長。
  2、異或運算:右側32位擴展置換爲48位後,與48位子密鑰做異或運算。
  3、S盒置換:將異或運算後的48位結果,分成8個6位的塊,每塊通過S盒置換產生4位的輸出,8個塊S盒置換後組成32位的輸出。S盒置換的過程爲:6位中取第1位和第6位組成行號,剩餘第2、3、4、5位組成列號,從S盒置換表中取出相應行、列的十進制數,並轉化爲4位二進制數,即爲S盒輸出。
  4、P盒置換:S盒置換後的32位輸出數據,進行P盒置換,仍然輸出爲32位數據。

  如下爲Feistel輪函數示意圖:
DES、3DES加密算法原理及其GO語言實現

  go標準庫中DES Feistel輪函數代碼如下:

func feistel(right uint32, key uint64) (result uint32) {
    //右側32位擴展置換爲48位,並與48位子密鑰做異或運算
    sBoxLocations := key ^ expandBlock(right)
    var sBoxResult uint32
    for i := uint8(0); i < 8; i++ {
        //sBoxLocations>>42、sBoxLocations <<= 6,按每6位分塊
        sBoxLocation := uint8(sBoxLocations>>42) & 0x3f
        sBoxLocations <<= 6
        //6位中取第1位和第6位組成行號
        row := (sBoxLocation & 0x1) | ((sBoxLocation & 0x20) >> 4)
        //剩餘第2、3、4、5位組成列號
        column := (sBoxLocation >> 1) & 0xf
        //feistelBox包括了S盒置換和P盒置換的實現
        sBoxResult ^= feistelBox[i][16*row+column]
    }
    return sBoxResult
}

var feistelBox [8][64]uint32
//P盒置換
func permuteBlock(src uint64, permutation []uint8) (block uint64) {
    for position, n := range permutation {
        bit := (src >> n) & 1
        block |= bit << uint((len(permutation)-1)-position)
    }
    return
}

//初始化feistelBox
func init() {
    for s := range sBoxes {
        for i := 0; i < 4; i++ {
            for j := 0; j < 16; j++ {
                f := uint64(sBoxes[s][i][j]) << (4 * (7 - uint(s)))
                f = permuteBlock(f, permutationFunction[:])
                feistelBox[s][16*i+j] = uint32(f)
            }
        }
    }
}
//代碼位置src/crypto/des/block.go

  附go標準庫中使用的擴展置換表和P盒置換表:

//擴展置換表
var expansionFunction = [48]byte{
    0, 31, 30, 29, 28, 27, 28, 27,
    26, 25, 24, 23, 24, 23, 22, 21,
    20, 19, 20, 19, 18, 17, 16, 15,
    16, 15, 14, 13, 12, 11, 12, 11,
    10, 9, 8, 7, 8, 7, 6, 5,
    4, 3, 4, 3, 2, 1, 0, 31,
}

//P盒置換表
var permutationFunction = [32]byte{
    16, 25, 12, 11, 3, 20, 4, 15,
    31, 17, 9, 6, 27, 14, 1, 22,
    30, 24, 8, 18, 0, 5, 29, 23,
    13, 19, 2, 26, 10, 21, 28, 7,
}
//代碼位置src/crypto/des/const.go

  附go標準庫中使用的S盒置換表:

var sBoxes = [8][4][16]uint8{
    // S-box 1
    {
        {14, 4, 13, 1, 2, 15, 11, 8, 3, 10, 6, 12, 5, 9, 0, 7},
        {0, 15, 7, 4, 14, 2, 13, 1, 10, 6, 12, 11, 9, 5, 3, 8},
        {4, 1, 14, 8, 13, 6, 2, 11, 15, 12, 9, 7, 3, 10, 5, 0},
        {15, 12, 8, 2, 4, 9, 1, 7, 5, 11, 3, 14, 10, 0, 6, 13},
    },
    // S-box 2
    {
        {15, 1, 8, 14, 6, 11, 3, 4, 9, 7, 2, 13, 12, 0, 5, 10},
        {3, 13, 4, 7, 15, 2, 8, 14, 12, 0, 1, 10, 6, 9, 11, 5},
        {0, 14, 7, 11, 10, 4, 13, 1, 5, 8, 12, 6, 9, 3, 2, 15},
        {13, 8, 10, 1, 3, 15, 4, 2, 11, 6, 7, 12, 0, 5, 14, 9},
    },
    // S-box 3
    {
        {10, 0, 9, 14, 6, 3, 15, 5, 1, 13, 12, 7, 11, 4, 2, 8},
        {13, 7, 0, 9, 3, 4, 6, 10, 2, 8, 5, 14, 12, 11, 15, 1},
        {13, 6, 4, 9, 8, 15, 3, 0, 11, 1, 2, 12, 5, 10, 14, 7},
        {1, 10, 13, 0, 6, 9, 8, 7, 4, 15, 14, 3, 11, 5, 2, 12},
    },
    // S-box 4
    {
        {7, 13, 14, 3, 0, 6, 9, 10, 1, 2, 8, 5, 11, 12, 4, 15},
        {13, 8, 11, 5, 6, 15, 0, 3, 4, 7, 2, 12, 1, 10, 14, 9},
        {10, 6, 9, 0, 12, 11, 7, 13, 15, 1, 3, 14, 5, 2, 8, 4},
        {3, 15, 0, 6, 10, 1, 13, 8, 9, 4, 5, 11, 12, 7, 2, 14},
    },
    // S-box 5
    {
        {2, 12, 4, 1, 7, 10, 11, 6, 8, 5, 3, 15, 13, 0, 14, 9},
        {14, 11, 2, 12, 4, 7, 13, 1, 5, 0, 15, 10, 3, 9, 8, 6},
        {4, 2, 1, 11, 10, 13, 7, 8, 15, 9, 12, 5, 6, 3, 0, 14},
        {11, 8, 12, 7, 1, 14, 2, 13, 6, 15, 0, 9, 10, 4, 5, 3},
    },
    // S-box 6
    {
        {12, 1, 10, 15, 9, 2, 6, 8, 0, 13, 3, 4, 14, 7, 5, 11},
        {10, 15, 4, 2, 7, 12, 9, 5, 6, 1, 13, 14, 0, 11, 3, 8},
        {9, 14, 15, 5, 2, 8, 12, 3, 7, 0, 4, 10, 1, 13, 11, 6},
        {4, 3, 2, 12, 9, 5, 15, 10, 11, 14, 1, 7, 6, 0, 8, 13},
    },
    // S-box 7
    {
        {4, 11, 2, 14, 15, 0, 8, 13, 3, 12, 9, 7, 5, 10, 6, 1},
        {13, 0, 11, 7, 4, 9, 1, 10, 14, 3, 5, 12, 2, 15, 8, 6},
        {1, 4, 11, 13, 12, 3, 7, 14, 10, 15, 6, 8, 0, 5, 9, 2},
        {6, 11, 13, 8, 1, 4, 10, 7, 9, 5, 0, 15, 14, 2, 3, 12},
    },
    // S-box 8
    {
        {13, 2, 8, 4, 6, 15, 11, 1, 10, 9, 3, 14, 5, 0, 12, 7},
        {1, 15, 13, 8, 10, 3, 7, 4, 12, 5, 6, 11, 0, 14, 9, 2},
        {7, 11, 4, 1, 9, 12, 14, 2, 0, 6, 10, 13, 15, 3, 5, 8},
        {2, 1, 14, 7, 4, 10, 8, 13, 15, 12, 9, 0, 3, 5, 6, 11},
    },
}
//代碼位置src/crypto/des/const.go

3DES

  DES是一個經典的對稱加密算法,但也缺陷明顯,即56位的密鑰安全性不足,已被證實可以在短時間內破解。爲解決此問題,出現了3DES,也稱Triple DES,3DES爲DES向AES過渡的加密算法,它使用3條56位的密鑰對數據進行三次加密。爲了兼容普通的DES,3DES並沒有直接使用加密->加密->加密的方式,而是採用了加密->解密->加密的方式。當三重密鑰均相同時,前兩步相互抵消,相當於僅實現了一次加密,因此可實現對普通DES加密算法的兼容。

  3DES解密過程,與加密過程相反,即逆序使用密鑰。
  如下爲三重DES示意圖:
DES、3DES加密算法原理及其GO語言實現
  如下爲3DES兼容DES示意圖:
DES、3DES加密算法原理及其GO語言實現
  go標準中3DES加密算法的實現如下:

type tripleDESCipher struct {
    cipher1, cipher2, cipher3 desCipher
}

func NewTripleDESCipher(key []byte) (cipher.Block, error) {
    if len(key) != 24 {
        return nil, KeySizeError(len(key))
    }

    c := new(tripleDESCipher)
    c.cipher1.generateSubkeys(key[:8])
    c.cipher2.generateSubkeys(key[8:16])
    c.cipher3.generateSubkeys(key[16:])
    return c, nil
}

//3DES加密
func (c *tripleDESCipher) Encrypt(dst, src []byte) {
    c.cipher1.Encrypt(dst, src)
    c.cipher2.Decrypt(dst, dst)
    c.cipher3.Encrypt(dst, dst)
}

//3DES解密
func (c *tripleDESCipher) Decrypt(dst, src []byte) {
    c.cipher3.Decrypt(dst, src)
    c.cipher2.Encrypt(dst, dst)
    c.cipher1.Decrypt(dst, dst)
}
//代碼位置src/crypto/des/cipher.go

後記

  相比DES,3DES因密鑰長度變長,安全性有所提高,但其處理速度不高。因此又出現了AES加密算法,AES較於3DES速度更快、安全性更高,後續單獨總結。

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