From c06fdb524cfe767494154cd2c5182a08a81fd8b4 Mon Sep 17 00:00:00 2001 From: Jonathan Turner Date: Sat, 23 Sep 2017 10:50:19 +0100 Subject: [PATCH] initial commit --- README.md | 16 +++++ aescts.go | 171 +++++++++++++++++++++++++++++++++++++++++++++++++ aescts_test.go | 44 +++++++++++++ 3 files changed, 231 insertions(+) create mode 100644 README.md create mode 100644 aescts.go create mode 100644 aescts_test.go diff --git a/README.md b/README.md new file mode 100644 index 0000000..d1fddf3 --- /dev/null +++ b/README.md @@ -0,0 +1,16 @@ +# AES CBC Ciphertext Stealing +[![GoDoc](https://godoc.org/gopkg.in/jcmturner/aescts.v1?status.svg)](https://godoc.org/gopkg.in/jcmturner/aescts.v1) [![Go Report Card](https://goreportcard.com/badge/gopkg.in/jcmturner/aescts.v1)](https://goreportcard.com/report/gopkg.in/jcmturner/aescts.v1) + +Encrypt and decrypt data using AES CBC Ciphertext stealing mode. + +Reference: https://en.wikipedia.org/wiki/Ciphertext_stealing#CBC_ciphertext_stealing + +To get the package, execute: +``` +go get gopkg.in/jcmturner/aescts.v1 +``` +To import this package, add the following line to your code: +```go +import "gopkg.in/jcmturner/aescts.v1" + +``` \ No newline at end of file diff --git a/aescts.go b/aescts.go new file mode 100644 index 0000000..164ce07 --- /dev/null +++ b/aescts.go @@ -0,0 +1,171 @@ +// Package aescts provides AES CBC CipherText Stealing encryption and decryption methods +package aescts + +import ( + "crypto/aes" + "crypto/cipher" + "errors" + "fmt" + "github.com/jcmturner/gokrb5/crypto/common" +) + +// Encrypt the message with the key and the initial vector. +// Returns: next iv, ciphertext bytes, error +func Encrypt(key, iv, plaintext []byte) ([]byte, []byte, error) { + l := len(plaintext) + + block, err := aes.NewCipher(key) + if err != nil { + return []byte{}, []byte{}, fmt.Errorf("Error creating cipher: %v", err) + } + mode := cipher.NewCBCEncrypter(block, iv) + + m := make([]byte, len(plaintext)) + copy(m, plaintext) + + /*For consistency, ciphertext stealing is always used for the last two + blocks of the data to be encrypted, as in [RC5]. If the data length + is a multiple of the block size, this is equivalent to plain CBC mode + with the last two ciphertext blocks swapped.*/ + /*The initial vector carried out from one encryption for use in a + subsequent encryption is the next-to-last block of the encryption + output; this is the encrypted form of the last plaintext block.*/ + if l <= aes.BlockSize { + m, _ = common.ZeroPad(m, aes.BlockSize) + mode.CryptBlocks(m, m) + return m, m, nil + } + if l%aes.BlockSize == 0 { + mode.CryptBlocks(m, m) + iv = m[len(m)-aes.BlockSize:] + rb, _ := swapLastTwoBlocks(m, aes.BlockSize) + return iv, rb, nil + } + m, _ = common.ZeroPad(m, aes.BlockSize) + rb, pb, lb, err := tailBlocks(m, aes.BlockSize) + if err != nil { + return []byte{}, []byte{}, fmt.Errorf("Error tailing blocks: %v", err) + } + var ct []byte + if rb != nil { + // Encrpt all but the lats 2 blocks and update the rolling iv + mode.CryptBlocks(rb, rb) + iv = rb[len(rb)-aes.BlockSize:] + mode = cipher.NewCBCEncrypter(block, iv) + ct = append(ct, rb...) + } + mode.CryptBlocks(pb, pb) + mode = cipher.NewCBCEncrypter(block, pb) + mode.CryptBlocks(lb, lb) + // Cipher Text Stealing (CTS) - Ref: https://en.wikipedia.org/wiki/Ciphertext_stealing#CBC_ciphertext_stealing + // Swap the last two cipher blocks + // Truncate the ciphertext to the length of the original plaintext + ct = append(ct, lb...) + ct = append(ct, pb...) + return lb, ct[:l], nil +} + +// Decrypt the ciphertext with the key and the initial vector. +func Decrypt(key, iv, ciphertext []byte) ([]byte, error) { + // Copy the cipher text as golang slices even when passed by value to this method can result in the backing arrays of the calling code value being updated. + ct := make([]byte, len(ciphertext)) + copy(ct, ciphertext) + if len(ct) < aes.BlockSize { + return []byte{}, fmt.Errorf("Ciphertext is not large enough. It is less that one block size. Blocksize:%v; Ciphertext:%v", aes.BlockSize, len(ct)) + } + // Configure the CBC + block, err := aes.NewCipher(key) + if err != nil { + return nil, fmt.Errorf("Error creating cipher: %v", err) + } + var mode cipher.BlockMode + + //If ciphertext is multiple of blocksize we just need to swap back the last two blocks and then do CBC + //If the ciphertext is just one block we can't swap so we just decrypt + if len(ct)%aes.BlockSize == 0 { + if len(ct) > aes.BlockSize { + ct, _ = swapLastTwoBlocks(ct, aes.BlockSize) + } + mode = cipher.NewCBCDecrypter(block, iv) + message := make([]byte, len(ct)) + mode.CryptBlocks(message, ct) + return message[:len(ct)], nil + } + + // Cipher Text Stealing (CTS) using CBC interface. Ref: https://en.wikipedia.org/wiki/Ciphertext_stealing#CBC_ciphertext_stealing + // Get ciphertext of the 2nd to last (penultimate) block (cpb), the last block (clb) and the rest (crb) + crb, cpb, clb, _ := tailBlocks(ct, aes.BlockSize) + v := make([]byte, len(iv), len(iv)) + copy(v, iv) + var message []byte + if crb != nil { + //If there is more than just the last and the penultimate block we decrypt it and the last bloc of this becomes the iv for later + rb := make([]byte, len(crb)) + mode = cipher.NewCBCDecrypter(block, v) + v = crb[len(crb)-aes.BlockSize:] + mode.CryptBlocks(rb, crb) + message = append(message, rb...) + } + + // We need to modify the cipher text + // Decryt the 2nd to last (penultimate) block with a the original iv + pb := make([]byte, aes.BlockSize) + mode = cipher.NewCBCDecrypter(block, iv) + mode.CryptBlocks(pb, cpb) + // number of byte needed to pad + npb := aes.BlockSize - len(ct)%aes.BlockSize + //pad last block using the number of bytes needed from the tail of the plaintext 2nd to last (penultimate) block + clb = append(clb, pb[len(pb)-npb:]...) + + // Now decrypt the last block in the penultimate position (iv will be from the crb, if the is no crb it's zeros) + // iv for the penultimate block decrypted in the last position becomes the modified last block + lb := make([]byte, aes.BlockSize) + mode = cipher.NewCBCDecrypter(block, v) + v = clb + mode.CryptBlocks(lb, clb) + message = append(message, lb...) + + // Now decrypt the penultimate block in the last position (iv will be from the modified last block) + mode = cipher.NewCBCDecrypter(block, v) + mode.CryptBlocks(cpb, cpb) + message = append(message, cpb...) + + // Truncate to the size of the original cipher text + return message[:len(ct)], nil +} + +func tailBlocks(b []byte, c int) ([]byte, []byte, []byte, error) { + if len(b) <= c { + return []byte{}, []byte{}, []byte{}, errors.New("bytes slice is not larger than one block so cannot tail") + } + // Get size of last block + var lbs int + if l := len(b) % aes.BlockSize; l == 0 { + lbs = aes.BlockSize + } else { + lbs = l + } + // Get last block + lb := b[len(b)-lbs:] + // Get 2nd to last (penultimate) block + pb := b[len(b)-lbs-c : len(b)-lbs] + if len(b) > 2*c { + rb := b[:len(b)-lbs-c] + return rb, pb, lb, nil + } + return nil, pb, lb, nil +} + +func swapLastTwoBlocks(b []byte, c int) ([]byte, error) { + rb, pb, lb, err := tailBlocks(b, c) + if err != nil { + return nil, err + } + var out []byte + if rb != nil { + out = append(out, rb...) + } + out = append(out, lb...) + out = append(out, pb...) + return out, nil +} diff --git a/aescts_test.go b/aescts_test.go new file mode 100644 index 0000000..1a1da4f --- /dev/null +++ b/aescts_test.go @@ -0,0 +1,44 @@ +package aescts + +import ( + "encoding/hex" + "github.com/stretchr/testify/assert" + "testing" +) + +func TestAesCts_Encrypt_Decrypt(t *testing.T) { + iv := make([]byte, 16) + key, _ := hex.DecodeString("636869636b656e207465726979616b69") + var tests = []struct { + plain string + cipher string + nextIV string + }{ + //Test vectors from RFC 3962 Appendix B + {"4920776f756c64206c696b652074686520", "c6353568f2bf8cb4d8a580362da7ff7f97", "c6353568f2bf8cb4d8a580362da7ff7f"}, + {"4920776f756c64206c696b65207468652047656e6572616c20476175277320", "fc00783e0efdb2c1d445d4c8eff7ed2297687268d6ecccc0c07b25e25ecfe5", "fc00783e0efdb2c1d445d4c8eff7ed22"}, + {"4920776f756c64206c696b65207468652047656e6572616c2047617527732043", "39312523a78662d5be7fcbcc98ebf5a897687268d6ecccc0c07b25e25ecfe584", "39312523a78662d5be7fcbcc98ebf5a8"}, + {"4920776f756c64206c696b65207468652047656e6572616c20476175277320436869636b656e2c20706c656173652c", "97687268d6ecccc0c07b25e25ecfe584b3fffd940c16a18c1b5549d2f838029e39312523a78662d5be7fcbcc98ebf5", "b3fffd940c16a18c1b5549d2f838029e"}, + {"4920776f756c64206c696b65207468652047656e6572616c20476175277320436869636b656e2c20706c656173652c20", "97687268d6ecccc0c07b25e25ecfe5849dad8bbb96c4cdc03bc103e1a194bbd839312523a78662d5be7fcbcc98ebf5a8", "9dad8bbb96c4cdc03bc103e1a194bbd8"}, + {"4920776f756c64206c696b65207468652047656e6572616c20476175277320436869636b656e2c20706c656173652c20616e6420776f6e746f6e20736f75702e", "97687268d6ecccc0c07b25e25ecfe58439312523a78662d5be7fcbcc98ebf5a84807efe836ee89a526730dbc2f7bc8409dad8bbb96c4cdc03bc103e1a194bbd8", "4807efe836ee89a526730dbc2f7bc840"}, + } + for i, test := range tests { + m, _ := hex.DecodeString(test.plain) + niv, c, err := Encrypt(key, iv, m) + if err != nil { + t.Errorf("Encryption failed for test %v: %v", i+1, err) + } + assert.Equal(t, test.cipher, hex.EncodeToString(c), "Encrypted result not as expected") + assert.Equal(t, test.nextIV, hex.EncodeToString(niv), "Next state IV not as expected") + } + //t.Log("AES CTS Encryption tests finished") + for i, test := range tests { + b, _ := hex.DecodeString(test.cipher) + p, err := Decrypt(key, iv, b) + if err != nil { + t.Errorf("Decryption failed for test %v: %v", i+1, err) + } + assert.Equal(t, test.plain, hex.EncodeToString(p), "Decrypted result not as expected") + } + //t.Log("AES CTS Decryption tests finished") +}