diff --git a/rc5/cipher.go b/rc5/cipher.go new file mode 100644 index 0000000..10f7153 --- /dev/null +++ b/rc5/cipher.go @@ -0,0 +1,45 @@ +package rc5 + +import ( + "crypto/cipher" + "fmt" +) + +// Reference: https://en.wikipedia.org/wiki/RC5 +// http://people.csail.mit.edu/rivest/Rivest-rc5rev.pdf + +// NewCipher creates and returns a new cipher.Block. +// The key argument should be the RC5 key, the wordSize arguement should be word size in bits, +// the r argument should be number of rounds. +func NewCipher(key []byte, wordSize, r uint) (cipher.Block, error) { + k := len(key) + switch k { + default: + return nil, fmt.Errorf("rc5: invalid key size %d, we support 16/24/32 now", k) + case 16, 24, 32: + break + } + if r < 8 || r > 127 { + return nil, fmt.Errorf("rc5: invalid rounds %d, should be between 8 and 127", r) + } + switch wordSize { + case 32: + return newCipher32(key, r) + case 64: + return newCipher64(key, r) + default: + return nil, fmt.Errorf("rc5: unsupported word size %d, support 32/64 now", wordSize) + } +} + +// NewCipher32 creates and returns a new cipher.Block with 32 bits word size. +// The key argument should be the RC5 key, the r argument should be number of rounds. +func NewCipher32(key []byte, r uint) (cipher.Block, error) { + return NewCipher(key, 32, r) +} + +// NewCipher64 creates and returns a new cipher.Block with 64 bits word size. +// The key argument should be the RC5 key, the r argument should be number of rounds. +func NewCipher64(key []byte, r uint) (cipher.Block, error) { + return NewCipher(key, 64, r) +} diff --git a/rc5/cipher32.go b/rc5/cipher32.go new file mode 100644 index 0000000..2b453ef --- /dev/null +++ b/rc5/cipher32.go @@ -0,0 +1,101 @@ +package rc5 + +import ( + "crypto/cipher" + "encoding/binary" + "math/bits" + + "github.com/emmansun/gmsm/internal/subtle" +) + +const ( + // BlockSize the RC5/32 block size in bytes. + BlockSize32 = 8 + P32 = 0xB7E15163 + Q32 = 0x9E3779B9 +) + +type rc5Cipher32 struct { + rounds uint + rk []uint32 +} + +func newCipher32(key []byte, rounds uint) (cipher.Block, error) { + c := &rc5Cipher32{} + c.rounds = rounds + c.rk = make([]uint32, (rounds+1)<<1) + expandKey32(key, c.rk) + return c, nil +} + +func expandKey32(key []byte, rk []uint32) { + roundKeys := len(rk) + // L is initially a c-length list of 0-valued w-length words + L := make([]uint32, len(key)/4) + lenL := len(L) + for i := 0; i < lenL; i++ { + L[i] = binary.LittleEndian.Uint32(key[:4]) + key = key[4:] + } + // Initialize key-independent pseudorandom S array + // S is initially a t=2(r+1) length list of undefined w-length words + rk[0] = P32 + for i := 1; i < roundKeys; i++ { + rk[i] = rk[i-1] + Q32 + } + // The main key scheduling loop + var A uint32 + var B uint32 + var i, j int + for k := 0; k < 3*roundKeys; k++ { + rk[i] = bits.RotateLeft32(rk[i]+(A+B), 3) + A = rk[i] + L[j] = bits.RotateLeft32(L[j]+(A+B), int(A+B)) + B = L[j] + i = (i + 1) % roundKeys + j = (j + 1) % lenL + } +} + +func (c *rc5Cipher32) BlockSize() int { return BlockSize32 } + +func (c *rc5Cipher32) Encrypt(dst, src []byte) { + if len(src) < BlockSize32 { + panic("rc5-32: input not full block") + } + if len(dst) < BlockSize32 { + panic("rc5-32: output not full block") + } + if subtle.InexactOverlap(dst[:BlockSize32], src[:BlockSize32]) { + panic("rc5-32: invalid buffer overlap") + } + A := binary.LittleEndian.Uint32(src[:4]) + c.rk[0] + B := binary.LittleEndian.Uint32(src[4:BlockSize32]) + c.rk[1] + + for r := 1; r <= int(c.rounds); r++ { + A = bits.RotateLeft32(A^B, int(B)) + c.rk[r<<1] + B = bits.RotateLeft32(B^A, int(A)) + c.rk[r<<1+1] + } + binary.LittleEndian.PutUint32(dst[:4], A) + binary.LittleEndian.PutUint32(dst[4:8], B) +} + +func (c *rc5Cipher32) Decrypt(dst, src []byte) { + if len(src) < BlockSize32 { + panic("rc5-32: input not full block") + } + if len(dst) < BlockSize32 { + panic("rc5-32: output not full block") + } + if subtle.InexactOverlap(dst[:BlockSize32], src[:BlockSize32]) { + panic("rc5-32: invalid buffer overlap") + } + A := binary.LittleEndian.Uint32(src[:4]) + B := binary.LittleEndian.Uint32(src[4:8]) + for r := c.rounds; r >= 1; r-- { + B = A ^ bits.RotateLeft32(B-c.rk[r<<1+1], -int(A)) + A = B ^ bits.RotateLeft32(A-c.rk[r<<1], -int(B)) + } + binary.LittleEndian.PutUint32(dst[:4], A-c.rk[0]) + binary.LittleEndian.PutUint32(dst[4:8], B-c.rk[1]) +} diff --git a/rc5/cipher32_test.go b/rc5/cipher32_test.go new file mode 100644 index 0000000..6b5f32e --- /dev/null +++ b/rc5/cipher32_test.go @@ -0,0 +1,93 @@ +package rc5 + +import ( + "encoding/hex" + "reflect" + "strings" + "testing" +) + +type Crypt32Test struct { + key string + in string + out string +} + +// http://people.csail.mit.edu/rivest/Rivest-rc5rev.pdf +// RC5-32/12/16 +var encrypt32Tests = []Crypt32Test{ + { + "00000000000000000000000000000000", + "0000000000000000", + "21A5DBEE154B8F6D", + }, + { + "915F4619BE41B2516355A50110A9CE91", + "21A5DBEE154B8F6D", + "F7C013AC5B2B8952", + }, + { + "783348E75AEB0F2FD7B169BB8DC16787", + "F7C013AC5B2B8952", + "2F42B3B70369FC92", + }, + { + "DC49DB1375A5584F6485B413B5F12BAF", + "2F42B3B70369FC92", + "65C178B284D197CC", + }, + { + "5269F149D41BA0152497574D7F153125", + "65C178B284D197CC", + "EB44E415DA319824", + }, +} + +func Test_rc5Cipher32_Encrypt(t *testing.T) { + for _, test := range encrypt32Tests { + key, _ := hex.DecodeString(test.key) + in, _ := hex.DecodeString(test.in) + target, _ := hex.DecodeString(test.out) + out := make([]byte, 8) + dst := make([]byte, 8) + c, err := NewCipher32(key, 12) + if err != nil { + t.Error(err) + } + + c.Encrypt(out, in) + if !reflect.DeepEqual(out, target) { + t.Errorf("expected=%v, result=%v\n", test.out, strings.ToUpper(hex.EncodeToString(out))) + } + c.Decrypt(dst, out) + if !reflect.DeepEqual(dst, in) { + t.Errorf("expected=%v, result=%v\n", test.in, strings.ToUpper(hex.EncodeToString(dst))) + } + } +} + +// https://tools.ietf.org/id/draft-krovetz-rc6-rc5-vectors-00.html#rfc.section.4 +func Test_RC5_322016(t *testing.T) { + testData := &Crypt32Test{ + "000102030405060708090A0B0C0D0E0F", + "0001020304050607", + "2A0EDC0E9431FF73", + } + key, _ := hex.DecodeString(testData.key) + in, _ := hex.DecodeString(testData.in) + target, _ := hex.DecodeString(testData.out) + out := make([]byte, 8) + dst := make([]byte, 8) + c, err := NewCipher32(key, 20) + if err != nil { + t.Error(err) + } + c.Encrypt(out, in) + if !reflect.DeepEqual(out, target) { + t.Errorf("expected=%v, result=%v\n", testData.out, strings.ToUpper(hex.EncodeToString(out))) + } + c.Decrypt(dst, out) + if !reflect.DeepEqual(dst, in) { + t.Errorf("expected=%v, result=%v\n", testData.in, strings.ToUpper(hex.EncodeToString(dst))) + } +} diff --git a/rc5/cipher64.go b/rc5/cipher64.go new file mode 100644 index 0000000..7552526 --- /dev/null +++ b/rc5/cipher64.go @@ -0,0 +1,101 @@ +package rc5 + +import ( + "crypto/cipher" + "encoding/binary" + "math/bits" + + "github.com/emmansun/gmsm/internal/subtle" +) + +const ( + // BlockSize the RC5/64 block size in bytes. + BlockSize64 = 16 + P64 = 0xB7E151628AED2A6B + Q64 = 0x9E3779B97F4A7C15 +) + +type rc5Cipher64 struct { + rounds uint + rk []uint64 +} + +func newCipher64(key []byte, rounds uint) (cipher.Block, error) { + c := &rc5Cipher64{} + c.rounds = rounds + c.rk = make([]uint64, (rounds+1)<<1) + expandKey64(key, c.rk) + return c, nil +} + +func expandKey64(key []byte, rk []uint64) { + roundKeys := len(rk) + // L is initially a c-length list of 0-valued w-length words + L := make([]uint64, len(key)/8) + lenL := len(L) + for i := 0; i < lenL; i++ { + L[i] = binary.LittleEndian.Uint64(key[:8]) + key = key[8:] + } + // Initialize key-independent pseudorandom S array + // S is initially a t=2(r+1) length list of undefined w-length words + rk[0] = P64 + for i := 1; i < roundKeys; i++ { + rk[i] = rk[i-1] + Q64 + } + // The main key scheduling loop + var A uint64 + var B uint64 + var i, j int + for k := 0; k < 3*roundKeys; k++ { + rk[i] = bits.RotateLeft64(rk[i]+(A+B), 3) + A = rk[i] + L[j] = bits.RotateLeft64(L[j]+(A+B), int(A+B)) + B = L[j] + i = (i + 1) % roundKeys + j = (j + 1) % lenL + } +} + +func (c *rc5Cipher64) BlockSize() int { return BlockSize64 } + +func (c *rc5Cipher64) Encrypt(dst, src []byte) { + if len(src) < BlockSize64 { + panic("rc5-64: input not full block") + } + if len(dst) < BlockSize64 { + panic("rc5-64: output not full block") + } + if subtle.InexactOverlap(dst[:BlockSize64], src[:BlockSize64]) { + panic("rc5-64: invalid buffer overlap") + } + A := binary.LittleEndian.Uint64(src[:8]) + c.rk[0] + B := binary.LittleEndian.Uint64(src[8:BlockSize64]) + c.rk[1] + + for r := 1; r <= int(c.rounds); r++ { + A = bits.RotateLeft64(A^B, int(B)) + c.rk[r<<1] + B = bits.RotateLeft64(B^A, int(A)) + c.rk[r<<1+1] + } + binary.LittleEndian.PutUint64(dst[:8], A) + binary.LittleEndian.PutUint64(dst[8:16], B) +} + +func (c *rc5Cipher64) Decrypt(dst, src []byte) { + if len(src) < BlockSize64 { + panic("rc5-64: input not full block") + } + if len(dst) < BlockSize64 { + panic("rc5-64: output not full block") + } + if subtle.InexactOverlap(dst[:BlockSize64], src[:BlockSize64]) { + panic("rc5-64: invalid buffer overlap") + } + A := binary.LittleEndian.Uint64(src[:8]) + B := binary.LittleEndian.Uint64(src[8:16]) + for r := c.rounds; r >= 1; r-- { + B = A ^ bits.RotateLeft64(B-c.rk[r<<1+1], -int(A)) + A = B ^ bits.RotateLeft64(A-c.rk[r<<1], -int(B)) + } + binary.LittleEndian.PutUint64(dst[:8], A-c.rk[0]) + binary.LittleEndian.PutUint64(dst[8:16], B-c.rk[1]) +} diff --git a/rc5/cipher64_test.go b/rc5/cipher64_test.go new file mode 100644 index 0000000..9e0f4e9 --- /dev/null +++ b/rc5/cipher64_test.go @@ -0,0 +1,34 @@ +package rc5 + +import ( + "encoding/hex" + "reflect" + "strings" + "testing" +) + +// https://tools.ietf.org/id/draft-krovetz-rc6-rc5-vectors-00.html#rfc.section.4 +func Test_RC5_642424(t *testing.T) { + testData := &Crypt32Test{ + "000102030405060708090A0B0C0D0E0F1011121314151617", + "000102030405060708090A0B0C0D0E0F", + "A46772820EDBCE0235ABEA32AE7178DA", + } + key, _ := hex.DecodeString(testData.key) + in, _ := hex.DecodeString(testData.in) + target, _ := hex.DecodeString(testData.out) + out := make([]byte, 16) + dst := make([]byte, 16) + c, err := NewCipher64(key, 24) + if err != nil { + t.Error(err) + } + c.Encrypt(out, in) + if !reflect.DeepEqual(out, target) { + t.Errorf("expected=%v, result=%v\n", testData.out, strings.ToUpper(hex.EncodeToString(out))) + } + c.Decrypt(dst, out) + if !reflect.DeepEqual(dst, in) { + t.Errorf("expected=%v, result=%v\n", testData.in, strings.ToUpper(hex.EncodeToString(dst))) + } +}