| // Package aescts provides AES CBC CipherText Stealing encryption and decryption methods |
| package aescts |
| |
| import ( |
| "crypto/aes" |
| "crypto/cipher" |
| "errors" |
| "fmt" |
| ) |
| |
| // 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, _ = 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, _ = 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 |
| } |
| |
| // zeroPad pads bytes with zeros to nearest multiple of message size m. |
| func zeroPad(b []byte, m int) ([]byte, error) { |
| if m <= 0 { |
| return nil, errors.New("Invalid message block size when padding") |
| } |
| if b == nil || len(b) == 0 { |
| return nil, errors.New("Data not valid to pad: Zero size") |
| } |
| if l := len(b) % m; l != 0 { |
| n := m - l |
| z := make([]byte, n) |
| b = append(b, z...) |
| } |
| return b, nil |
| } |