// Package aescts provides AES CBC CipherText Stealing encryption and decryption methods
package aescts import ( ) // Encrypt the message with the key and the initial vector. // Returns: next iv, ciphertext bytes, error func (, , []byte) ([]byte, []byte, error) { := len() , := aes.NewCipher() if != nil { return []byte{}, []byte{}, fmt.Errorf("error creating cipher: %v", ) } := cipher.NewCBCEncrypter(, ) := make([]byte, len()) copy(, ) /*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 <= aes.BlockSize { , _ = zeroPad(, aes.BlockSize) .CryptBlocks(, ) return , , nil } if %aes.BlockSize == 0 { .CryptBlocks(, ) = [len()-aes.BlockSize:] , := swapLastTwoBlocks(, aes.BlockSize) return , , nil } , _ = zeroPad(, aes.BlockSize) , , , := tailBlocks(, aes.BlockSize) if != nil { return []byte{}, []byte{}, fmt.Errorf("error tailing blocks: %v", ) } var []byte if != nil { // Encrpt all but the lats 2 blocks and update the rolling iv .CryptBlocks(, ) = [len()-aes.BlockSize:] = cipher.NewCBCEncrypter(, ) = append(, ...) } .CryptBlocks(, ) = cipher.NewCBCEncrypter(, ) .CryptBlocks(, ) // 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 = append(, ...) = append(, ...) return , [:], nil } // Decrypt the ciphertext with the key and the initial vector. func (, , []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. := make([]byte, len()) copy(, ) if len() < 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()) } // Configure the CBC , := aes.NewCipher() if != nil { return nil, fmt.Errorf("error creating cipher: %v", ) } var 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()%aes.BlockSize == 0 { if len() > aes.BlockSize { , _ = swapLastTwoBlocks(, aes.BlockSize) } = cipher.NewCBCDecrypter(, ) := make([]byte, len()) .CryptBlocks(, ) return [:len()], 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) , , , := tailBlocks(, aes.BlockSize) := make([]byte, len(), len()) copy(, ) var []byte if != 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 := make([]byte, len()) = cipher.NewCBCDecrypter(, ) = [len()-aes.BlockSize:] .CryptBlocks(, ) = append(, ...) } // We need to modify the cipher text // Decryt the 2nd to last (penultimate) block with a the original iv := make([]byte, aes.BlockSize) = cipher.NewCBCDecrypter(, ) .CryptBlocks(, ) // number of byte needed to pad := aes.BlockSize - len()%aes.BlockSize //pad last block using the number of bytes needed from the tail of the plaintext 2nd to last (penultimate) block = append(, [len()-:]...) // 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 := make([]byte, aes.BlockSize) = cipher.NewCBCDecrypter(, ) = .CryptBlocks(, ) = append(, ...) // Now decrypt the penultimate block in the last position (iv will be from the modified last block) = cipher.NewCBCDecrypter(, ) .CryptBlocks(, ) = append(, ...) // Truncate to the size of the original cipher text return [:len()], nil } func tailBlocks( []byte, int) ([]byte, []byte, []byte, error) { if len() <= { return []byte{}, []byte{}, []byte{}, errors.New("bytes slice is not larger than one block so cannot tail") } // Get size of last block var int if := len() % aes.BlockSize; == 0 { = aes.BlockSize } else { = } // Get last block := [len()-:] // Get 2nd to last (penultimate) block := [len()-- : len()-] if len() > 2* { := [:len()--] return , , , nil } return nil, , , nil } func swapLastTwoBlocks( []byte, int) ([]byte, error) { , , , := tailBlocks(, ) if != nil { return nil, } var []byte if != nil { = append(, ...) } = append(, ...) = append(, ...) return , nil } // zeroPad pads bytes with zeros to nearest multiple of message size m. func zeroPad( []byte, int) ([]byte, error) { if <= 0 { return nil, errors.New("invalid message block size when padding") } if == nil || len() == 0 { return nil, errors.New("data not valid to pad: Zero size") } if := len() % ; != 0 { := - := make([]byte, ) = append(, ...) } return , nil }