-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
3 changed files
with
231 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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" | ||
|
||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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") | ||
} |