]> Cypherpunks repositories - gostls13.git/commitdiff
crypto/des: improve the throughput of DES and 3DES
authorMura Li <mura_li@castech.com.tw>
Mon, 29 Aug 2016 02:22:20 +0000 (10:22 +0800)
committerRuss Cox <rsc@golang.org>
Tue, 7 Feb 2017 14:17:05 +0000 (14:17 +0000)
For detailed explanation of the adopted (Eric Young's) algorithm,
see http://ftp.nluug.nl/security/coast/libs/libdes/ALGORITHM

benchmark                   old ns/op     new ns/op     delta
BenchmarkEncrypt-16         649           164           -74.73%
BenchmarkDecrypt-16         546           156           -71.43%
BenchmarkTDESEncrypt-16     1651          385           -76.68%
BenchmarkTDESDecrypt-16     1645          378           -77.02%

benchmark                   old MB/s     new MB/s     speedup
BenchmarkEncrypt-16         12.31        48.76        3.96x
BenchmarkDecrypt-16         14.64        51.03        3.49x
BenchmarkTDESEncrypt-16     4.84         20.74        4.29x
BenchmarkTDESDecrypt-16     4.86         21.16        4.35x

Change-Id: Ic3e1fe3340419ec5a0e6379434911eb41e0246f6
Reviewed-on: https://go-review.googlesource.com/36490
Run-TryBot: Minux Ma <minux@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Russ Cox <rsc@golang.org>
src/crypto/des/block.go
src/crypto/des/cipher.go
src/crypto/des/des_test.go

index 99338d62a6e0d35bdfb6bb61d44ce27d0151880d..21e6d4e82f6b2cc8cea0b0e8da2ea4d2679ecb19 100644 (file)
@@ -4,25 +4,29 @@
 
 package des
 
-import (
-       "encoding/binary"
-)
+import "encoding/binary"
 
 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
-       for i := 0; i < 16; i++ {
-               if decrypt {
-                       subkey = subkeys[15-i]
-               } else {
-                       subkey = subkeys[i]
-               }
+       left = (left << 1) | (left >> 31)
+       right = (right << 1) | (right >> 31)
 
-               left, right = right, left^feistel(right, subkey)
+       if decrypt {
+               for i := 0; i < 8; i++ {
+                       left, right = feistel(left, right, subkeys[15-2*i], subkeys[15-(2*i+1)])
+               }
+       } else {
+               for i := 0; i < 8; i++ {
+                       left, right = feistel(left, right, subkeys[2*i], subkeys[2*i+1])
+               }
        }
+
+       left = (left << 31) | (left >> 1)
+       right = (right << 31) | (right >> 1)
+
        // switch left & right and perform final permutation
        preOutput := (uint64(right) << 32) | uint64(left)
        binary.BigEndian.PutUint64(dst, permuteFinalBlock(preOutput))
@@ -39,19 +43,34 @@ func decryptBlock(subkeys []uint64, dst, src []byte) {
 }
 
 // DES Feistel function
-func feistel(right uint32, key uint64) (result uint32) {
-       sBoxLocations := key ^ expandBlock(right)
-       var sBoxResult uint32
-       for i := uint8(0); i < 8; i++ {
-               sBoxLocation := uint8(sBoxLocations>>42) & 0x3f
-               sBoxLocations <<= 6
-               // row determined by 1st and 6th bit
-               // column is middle four bits
-               row := (sBoxLocation & 0x1) | ((sBoxLocation & 0x20) >> 4)
-               column := (sBoxLocation >> 1) & 0xf
-               sBoxResult ^= feistelBox[i][16*row+column]
-       }
-       return sBoxResult
+func feistel(l, r uint32, k0, k1 uint64) (lout, rout uint32) {
+       var t uint32
+
+       t = r ^ uint32(k0>>32)
+       l ^= feistelBox[7][t&0x3f] ^
+               feistelBox[5][(t>>8)&0x3f] ^
+               feistelBox[3][(t>>16)&0x3f] ^
+               feistelBox[1][(t>>24)&0x3f]
+
+       t = ((r << 28) | (r >> 4)) ^ uint32(k0)
+       l ^= feistelBox[6][(t)&0x3f] ^
+               feistelBox[4][(t>>8)&0x3f] ^
+               feistelBox[2][(t>>16)&0x3f] ^
+               feistelBox[0][(t>>24)&0x3f]
+
+       t = l ^ uint32(k1>>32)
+       r ^= feistelBox[7][t&0x3f] ^
+               feistelBox[5][(t>>8)&0x3f] ^
+               feistelBox[3][(t>>16)&0x3f] ^
+               feistelBox[1][(t>>24)&0x3f]
+
+       t = ((l << 28) | (l >> 4)) ^ uint32(k1)
+       r ^= feistelBox[6][(t)&0x3f] ^
+               feistelBox[4][(t>>8)&0x3f] ^
+               feistelBox[2][(t>>16)&0x3f] ^
+               feistelBox[0][(t>>24)&0x3f]
+
+       return l, r
 }
 
 // feistelBox[s][16*i+j] contains the output of permutationFunction
@@ -73,27 +92,22 @@ func init() {
                        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)
+
+                               // Row is determined by the 1st and 6th bit.
+                               // Column is the middle four bits.
+                               row := uint8(((i & 2) << 4) | i&1)
+                               col := uint8(j << 1)
+                               t := row | col
+
+                               // The rotation was performed in the feistel rounds, being factored out and now mixed into the feistelBox.
+                               f = (f << 1) | (f >> 31)
+
+                               feistelBox[s][t] = uint32(f)
                        }
                }
        }
 }
 
-// expandBlock expands an input block of 32 bits,
-// producing an output block of 48 bits.
-func expandBlock(src uint32) (block uint64) {
-       // rotate the 5 highest bits to the right.
-       src = (src << 5) | (src >> 27)
-       for i := 0; i < 8; i++ {
-               block <<= 6
-               // take the 6 bits on the right
-               block |= uint64(src) & (1<<6 - 1)
-               // advance by 4 bits.
-               src = (src << 4) | (src >> 28)
-       }
-       return
-}
-
 // permuteInitialBlock is equivalent to the permutation defined
 // by initialPermutation.
 func permuteInitialBlock(block uint64) uint64 {
@@ -218,6 +232,24 @@ func (c *desCipher) generateSubkeys(keyBytes []byte) {
                // combine halves to form 56-bit input to PC2
                pc2Input := uint64(leftRotations[i])<<28 | uint64(rightRotations[i])
                // apply PC2 permutation to 7 byte input
-               c.subkeys[i] = permuteBlock(pc2Input, permutedChoice2[:])
+               c.subkeys[i] = unpack(permuteBlock(pc2Input, permutedChoice2[:]))
        }
 }
+
+// Expand 48-bit input to 64-bit, with each 6-bit block padded by extra two bits at the top.
+// By doing so, we can have the input blocks (four bits each), and the key blocks (six bits each) well-aligned without
+// extra shifts/rotations for alignments.
+func unpack(x uint64) uint64 {
+       var result uint64
+
+       result = ((x>>(6*1))&0xff)<<(8*0) |
+               ((x>>(6*3))&0xff)<<(8*1) |
+               ((x>>(6*5))&0xff)<<(8*2) |
+               ((x>>(6*7))&0xff)<<(8*3) |
+               ((x>>(6*0))&0xff)<<(8*4) |
+               ((x>>(6*2))&0xff)<<(8*5) |
+               ((x>>(6*4))&0xff)<<(8*6) |
+               ((x>>(6*6))&0xff)<<(8*7)
+
+       return result
+}
index 2f929ca7bed0356e337c85699ebb1d153ef6f4d7..46af5b0f022c8816781d4ab9c582f28f4cc36667 100644 (file)
@@ -6,6 +6,7 @@ package des
 
 import (
        "crypto/cipher"
+       "encoding/binary"
        "strconv"
 )
 
@@ -61,13 +62,51 @@ func NewTripleDESCipher(key []byte) (cipher.Block, error) {
 func (c *tripleDESCipher) BlockSize() int { return BlockSize }
 
 func (c *tripleDESCipher) Encrypt(dst, src []byte) {
-       c.cipher1.Encrypt(dst, src)
-       c.cipher2.Decrypt(dst, dst)
-       c.cipher3.Encrypt(dst, dst)
+       b := binary.BigEndian.Uint64(src)
+       b = permuteInitialBlock(b)
+       left, right := uint32(b>>32), uint32(b)
+
+       left = (left << 1) | (left >> 31)
+       right = (right << 1) | (right >> 31)
+
+       for i := 0; i < 8; i++ {
+               left, right = feistel(left, right, c.cipher1.subkeys[2*i], c.cipher1.subkeys[2*i+1])
+       }
+       for i := 0; i < 8; i++ {
+               right, left = feistel(right, left, c.cipher2.subkeys[15-2*i], c.cipher2.subkeys[15-(2*i+1)])
+       }
+       for i := 0; i < 8; i++ {
+               left, right = feistel(left, right, c.cipher3.subkeys[2*i], c.cipher3.subkeys[2*i+1])
+       }
+
+       left = (left << 31) | (left >> 1)
+       right = (right << 31) | (right >> 1)
+
+       preOutput := (uint64(right) << 32) | uint64(left)
+       binary.BigEndian.PutUint64(dst, permuteFinalBlock(preOutput))
 }
 
 func (c *tripleDESCipher) Decrypt(dst, src []byte) {
-       c.cipher3.Decrypt(dst, src)
-       c.cipher2.Encrypt(dst, dst)
-       c.cipher1.Decrypt(dst, dst)
+       b := binary.BigEndian.Uint64(src)
+       b = permuteInitialBlock(b)
+       left, right := uint32(b>>32), uint32(b)
+
+       left = (left << 1) | (left >> 31)
+       right = (right << 1) | (right >> 31)
+
+       for i := 0; i < 8; i++ {
+               left, right = feistel(left, right, c.cipher3.subkeys[15-2*i], c.cipher3.subkeys[15-(2*i+1)])
+       }
+       for i := 0; i < 8; i++ {
+               right, left = feistel(right, left, c.cipher2.subkeys[2*i], c.cipher2.subkeys[2*i+1])
+       }
+       for i := 0; i < 8; i++ {
+               left, right = feistel(left, right, c.cipher1.subkeys[15-2*i], c.cipher1.subkeys[15-(2*i+1)])
+       }
+
+       left = (left << 31) | (left >> 1)
+       right = (right << 31) | (right >> 1)
+
+       preOutput := (uint64(right) << 32) | uint64(left)
+       binary.BigEndian.PutUint64(dst, permuteFinalBlock(preOutput))
 }
index 2bd525afecce9503b36672031ab8d50f386636e5..690a49f5efb05a6e8aa065369e8958b2bf0d92a0 100644 (file)
@@ -1526,17 +1526,6 @@ func TestFinalPermute(t *testing.T) {
        }
 }
 
-func TestExpandBlock(t *testing.T) {
-       for i := uint(0); i < 32; i++ {
-               bit := uint32(1) << i
-               got := expandBlock(bit)
-               want := permuteBlock(uint64(bit), expansionFunction[:])
-               if got != want {
-                       t.Errorf("expand(%x) = %x, want %x", bit, got, want)
-               }
-       }
-}
-
 func BenchmarkEncrypt(b *testing.B) {
        tt := encryptDESTests[0]
        c, err := NewCipher(tt.key)
@@ -1564,3 +1553,31 @@ func BenchmarkDecrypt(b *testing.B) {
                c.Decrypt(out, tt.out)
        }
 }
+
+func BenchmarkTDESEncrypt(b *testing.B) {
+       tt := encryptTripleDESTests[0]
+       c, err := NewTripleDESCipher(tt.key)
+       if err != nil {
+               b.Fatal("NewCipher:", err)
+       }
+       out := make([]byte, len(tt.in))
+       b.SetBytes(int64(len(out)))
+       b.ResetTimer()
+       for i := 0; i < b.N; i++ {
+               c.Encrypt(out, tt.in)
+       }
+}
+
+func BenchmarkTDESDecrypt(b *testing.B) {
+       tt := encryptTripleDESTests[0]
+       c, err := NewTripleDESCipher(tt.key)
+       if err != nil {
+               b.Fatal("NewCipher:", err)
+       }
+       out := make([]byte, len(tt.out))
+       b.SetBytes(int64(len(out)))
+       b.ResetTimer()
+       for i := 0; i < b.N; i++ {
+               c.Decrypt(out, tt.out)
+       }
+}