Skip to content

Instantly share code, notes, and snippets.

@slifer2015
Last active October 30, 2018 12:40
Show Gist options
  • Save slifer2015/0d182d99280758a154e3fe12e48664d4 to your computer and use it in GitHub Desktop.
Save slifer2015/0d182d99280758a154e3fe12e48664d4 to your computer and use it in GitHub Desktop.
AES 256 CBC 32byte block size IV support golang implementation
package encryption
import (
"crypto/cipher"
"runtime"
"unsafe"
)
func dup(p []byte) []byte {
q := make([]byte, len(p))
copy(q, p)
return q
}
type cbc struct {
b cipher.Block
blockSize int
iv []byte
tmp []byte
}
func newCBC(b cipher.Block, iv []byte) *cbc {
return &cbc{
b: b,
blockSize: b.BlockSize(),
iv: dup(iv),
tmp: make([]byte, b.BlockSize()),
}
}
type cbcEncrypter cbc
// cbcEncAble is an interface implemented by ciphers that have a specific
// optimized implementation of CBC encryption, like crypto/aes.
// NewCBCEncrypter will check for this interface and return the specific
// BlockMode if found.
type cbcEncAble interface {
NewCBCEncrypter(iv []byte) cipher.BlockMode
}
// NewCBCEncrypter returns a BlockMode which encrypts in cipher block chaining
// mode, using the given Block. The length of iv must be the same as the
// Block's block size.
func NewCBCEncrypter(b cipher.Block, iv []byte) cipher.BlockMode {
if len(iv) != b.BlockSize() {
panic("cipher.NewCBCEncrypter: IV length must equal block size")
}
if cbc, ok := b.(cbcEncAble); ok {
return cbc.NewCBCEncrypter(iv)
}
return (*cbcEncrypter)(newCBC(b, iv))
}
func (x *cbcEncrypter) BlockSize() int { return x.blockSize }
func (x *cbcEncrypter) CryptBlocks(dst, src []byte) {
if len(src)%x.blockSize != 0 {
panic("crypto/cipher: input not full blocks")
}
if len(dst) < len(src) {
panic("crypto/cipher: output smaller than input")
}
iv := x.iv
for len(src) > 0 {
// Write the xor to dst, then encrypt in place.
xorBytes(dst[:x.blockSize], src[:x.blockSize], iv)
x.b.Encrypt(dst[:x.blockSize], dst[:x.blockSize])
// Move to the next block with this block as the next iv.
iv = dst[:x.blockSize]
src = src[x.blockSize:]
dst = dst[x.blockSize:]
}
// Save the iv for the next CryptBlocks call.
copy(x.iv, iv)
}
func (x *cbcEncrypter) SetIV(iv []byte) {
if len(iv) != len(x.iv) {
panic("cipher: incorrect length IV")
}
copy(x.iv, iv)
}
type cbcDecrypter cbc
// cbcDecAble is an interface implemented by ciphers that have a specific
// optimized implementation of CBC decryption, like crypto/aes.
// NewCBCDecrypter will check for this interface and return the specific
// BlockMode if found.
type cbcDecAble interface {
NewCBCDecrypter(iv []byte) cipher.BlockMode
}
// NewCBCDecrypter returns a BlockMode which decrypts in cipher block chaining
// mode, using the given Block. The length of iv must be the same as the
// Block's block size and must match the iv used to encrypt the data.
func NewCBCDecrypter(b cipher.Block, iv []byte) cipher.BlockMode {
if len(iv) != b.BlockSize() {
panic("cipher.NewCBCDecrypter: IV length must equal block size")
}
if cbc, ok := b.(cbcDecAble); ok {
return cbc.NewCBCDecrypter(iv)
}
return (*cbcDecrypter)(newCBC(b, iv))
}
func (x *cbcDecrypter) BlockSize() int { return x.blockSize }
func (x *cbcDecrypter) CryptBlocks(dst, src []byte) {
if len(src)%x.blockSize != 0 {
panic("crypto/cipher: input not full blocks")
}
if len(dst) < len(src) {
panic("crypto/cipher: output smaller than input")
}
if len(src) == 0 {
return
}
// For each block, we need to xor the decrypted data with the previous block's ciphertext (the iv).
// To avoid making a copy each time, we loop over the blocks BACKWARDS.
end := len(src)
start := end - x.blockSize
prev := start - x.blockSize
// Copy the last block of ciphertext in preparation as the new iv.
copy(x.tmp, src[start:end])
// Loop over all but the first block.
for start > 0 {
x.b.Decrypt(dst[start:end], src[start:end])
xorBytes(dst[start:end], dst[start:end], src[prev:start])
end = start
start = prev
prev -= x.blockSize
}
// The first block is special because it uses the saved iv.
x.b.Decrypt(dst[start:end], src[start:end])
xorBytes(dst[start:end], dst[start:end], x.iv)
// Set the new iv to the first block we copied earlier.
x.iv, x.tmp = x.tmp, x.iv
}
func (x *cbcDecrypter) SetIV(iv []byte) {
if len(iv) != len(x.iv) {
panic("cipher: incorrect length IV")
}
copy(x.iv, iv)
}
const wordSize = int(unsafe.Sizeof(uintptr(0)))
const supportsUnaligned = runtime.GOARCH == "386" || runtime.GOARCH == "amd64" || runtime.GOARCH == "ppc64" || runtime.GOARCH == "ppc64le" || runtime.GOARCH == "s390x"
// fastXORBytes xors in bulk. It only works on architectures that
// support unaligned read/writes.
func fastXORBytes(dst, a, b []byte) int {
n := len(a)
if len(b) < n {
n = len(b)
}
if n == 0 {
return 0
}
// Assert dst has enough space
_ = dst[n-1]
w := n / wordSize
if w > 0 {
dw := *(*[]uintptr)(unsafe.Pointer(&dst))
aw := *(*[]uintptr)(unsafe.Pointer(&a))
bw := *(*[]uintptr)(unsafe.Pointer(&b))
for i := 0; i < w; i++ {
dw[i] = aw[i] ^ bw[i]
}
}
for i := (n - n%wordSize); i < n; i++ {
dst[i] = a[i] ^ b[i]
}
return n
}
func safeXORBytes(dst, a, b []byte) int {
n := len(a)
if len(b) < n {
n = len(b)
}
for i := 0; i < n; i++ {
dst[i] = a[i] ^ b[i]
}
return n
}
// xorBytes xors the bytes in a and b. The destination should have enough
// space, otherwise xorBytes will panic. Returns the number of bytes xor'd.
func xorBytes(dst, a, b []byte) int {
if supportsUnaligned {
return fastXORBytes(dst, a, b)
} else {
// TODO(hanwen): if (dst, a, b) have common alignment
// we could still try fastXORBytes. It is not clear
// how often this happens, and it's only worth it if
// the block encryption itself is hardware
// accelerated.
return safeXORBytes(dst, a, b)
}
}
// fastXORWords XORs multiples of 4 or 8 bytes (depending on architecture.)
// The arguments are assumed to be of equal length.
func fastXORWords(dst, a, b []byte) {
dw := *(*[]uintptr)(unsafe.Pointer(&dst))
aw := *(*[]uintptr)(unsafe.Pointer(&a))
bw := *(*[]uintptr)(unsafe.Pointer(&b))
n := len(b) / wordSize
for i := 0; i < n; i++ {
dw[i] = aw[i] ^ bw[i]
}
}
func xorWords(dst, a, b []byte) {
if supportsUnaligned {
fastXORWords(dst, a, b)
} else {
safeXORBytes(dst, a, b)
}
}
package encryption
import (
"bytes"
"encoding/base64"
"fmt"
)
func Rijndael256CBCEncrypt32(key,iv []byte,src string)(string,error){
ciph,err:=NewCipher(key)
if err!=nil{
return "",err
}
origData:=addPadding([]byte(src),ciph.BlockSize())
blockMode:=NewCBCEncrypter(ciph,iv)
crypted := make([]byte, len(origData))
blockMode.CryptBlocks(crypted, origData)
finalResult:=base64.StdEncoding.EncodeToString(crypted)
return finalResult,nil
}
func Rijndael256CBCDecrypt32(key,iv []byte,cipherText string)(string,error){
ciph,err:=NewCipher(key)
if err!=nil{
return "",err
}
cipherTextFinal,err:=base64.StdEncoding.DecodeString(cipherText)
if err!=nil{
return "",err
}
blockMode:=NewCBCDecrypter(ciph,iv)
origData := make([]byte, len(cipherTextFinal))
fmt.Println(len(origData))
blockMode.CryptBlocks(origData, cipherTextFinal)
origData, err = unpad(origData, ciph.BlockSize())
return string(origData),nil
}
func addPadding(src []byte, blockLen int)[]byte{
padding := blockLen - len(src)%blockLen
padtext := bytes.Repeat([]byte{byte(padding)}, padding)
return append(src, padtext...)
}
func unpad(data []byte, blockLen int) ([]byte, error) {
if blockLen <= 0 {
return nil, fmt.Errorf("invalid blockLen %d", blockLen)
}
if len(data)%blockLen != 0 || len(data) == 0 {
return nil, fmt.Errorf("invalid data len %d", len(data))
}
padlen := int(data[len(data)-1])
if padlen > blockLen || padlen == 0 {
return nil, fmt.Errorf("invalid padding")
}
// check padding
pad := data[len(data)-padlen:]
for i := 0; i < padlen; i++ {
if pad[i] != byte(padlen) {
return nil, fmt.Errorf("invalid padding")
}
}
return data[:len(data)-padlen], nil
}
package encryption
import (
"crypto/cipher"
"encoding/binary"
)
const rounds = 14
const keyWords = 8
const BlockSize = 32
const blockWords = BlockSize/4
type Cipher struct {
key [blockWords * (rounds + 1)]uint32
}
func NewCipher(key []byte) (cipher.Block, error) {
c := new(Cipher)
for i := 0; i < keyWords; i++ {
c.key[i] = binary.BigEndian.Uint32(key[4*i:])
}
roundConstant := byte(1)
for i := keyWords; i < blockWords * (rounds + 1); i++ {
temp := c.key[i-1]
if i % keyWords == 0 {
temp = subByte(rotByte(temp)) ^ (uint32(roundConstant) << 24)
roundConstant = xtime(roundConstant)
} else if i % keyWords == 4 {
temp = subByte(temp)
}
c.key[i] = c.key[i - keyWords] ^ temp
}
return c, nil
}
func (c *Cipher) BlockSize() int {
return BlockSize;
}
func (c *Cipher) Encrypt(dst, src []byte) {
var state [4*blockWords]byte
copy(state[:], src[:])
addKey(&state, c.key[:])
for round := 1; round < rounds; round++ {
// ByteSub
for i := range state {
state[i] = sbox[state[i]]
}
// ShiftRow
if blockWords == 32/4 {
shiftRow(&state, 1, 1)
shiftRow(&state, 2, 3)
shiftRow(&state, 3, 4)
} else {
shiftRow(&state, 1, 1)
shiftRow(&state, 2, 2)
shiftRow(&state, 3, 3)
}
// MixColumns
for i := 0; i < blockWords; i++ {
mixColumn(state[4*i:])
}
addKey(&state, c.key[blockWords*round:])
}
// ByteSub
for i := range state {
state[i] = sbox[state[i]]
}
// ShiftRow
if blockWords == 32/4 {
shiftRow(&state, 1, 1)
shiftRow(&state, 2, 3)
shiftRow(&state, 3, 4)
} else {
shiftRow(&state, 1, 1)
shiftRow(&state, 2, 2)
shiftRow(&state, 3, 3)
}
addKey(&state, c.key[blockWords*rounds:])
copy(dst[:], state[:])
}
func (c *Cipher) Decrypt(dst, src []byte) {
var state [4*blockWords]byte
copy(state[:], src[:])
addKey(&state, c.key[blockWords*rounds:])
// ShiftRow
if blockWords == 32/4 {
shiftRow(&state, 1, -1)
shiftRow(&state, 2, -3)
shiftRow(&state, 3, -4)
} else {
shiftRow(&state, 1, -1)
shiftRow(&state, 2, -2)
shiftRow(&state, 3, -3)
}
// ByteSub
for i := range state {
state[i] = sboxInv[state[i]]
}
for round := rounds - 1; round >= 1; round-- {
addKey(&state, c.key[blockWords*round:])
// MixColumns
for i := 0; i < blockWords; i++ {
mixColumnInv(state[4*i:])
}
// ShiftRow
if blockWords == 32/4 {
shiftRow(&state, 1, -1)
shiftRow(&state, 2, -3)
shiftRow(&state, 3, -4)
} else {
shiftRow(&state, 1, -1)
shiftRow(&state, 2, -2)
shiftRow(&state, 3, -3)
}
// ByteSub
for i := range state {
state[i] = sboxInv[state[i]]
}
}
addKey(&state, c.key[:])
copy(dst[:], state[:])
}
func subByte(a uint32) uint32 {
ret := uint32(0)
for i := 0; i < 4; i++ {
b := byte(a)
ret >>= 8
ret |= uint32(sbox[b]) << 24
a >>= 8
}
return ret
}
func rotByte(a uint32) uint32 {
return (a >> 24) | (a << 8)
}
func addKey(state *[4*blockWords]byte, key []uint32) {
for i := 0; i < blockWords; i++ {
t := binary.BigEndian.Uint32(state[4*i:])
t ^= key[i]
binary.BigEndian.PutUint32(state[4*i:], t)
}
}
func shiftRow(state *[4*blockWords]byte, row, shift int) {
var input [blockWords]byte
for i := range input {
input[i] = state[row + 4*i]
}
src := shift
if src < 0 {
src += blockWords
}
dst := row
for i := 0; i < blockWords; i++ {
state[dst] = input[src]
dst += 4
dst %= blockWords*4
src++
src %= blockWords
}
}
func mixColumn(col []byte) {
a := col[0]
b := col[1]
c := col[2]
d := col[3]
e := a ^ b ^ c ^ d
col[0] ^= e ^ xtime(a^b)
col[1] ^= e ^ xtime(b^c)
col[2] ^= e ^ xtime(c^d)
col[3] ^= e ^ xtime(d^a)
}
func mixColumnInv(col []byte) {
a := col[0]
b := col[1]
c := col[2]
d := col[3]
e := a ^ b ^ c ^ d
z := xtime(e)
x := e ^ xtime(xtime(z^a^c))
y := e ^ xtime(xtime(z^b^d))
col[0] ^= x ^ xtime(a^b)
col[1] ^= y ^ xtime(b^c)
col[2] ^= x ^ xtime(c^d)
col[3] ^= y ^ xtime(d^a)
}
func xtime(b byte) byte {
c := uint32(b)
c <<= 1
c ^= ((c >> 8) & 1) * 0x1b
return byte(c)
}
// FIPS-197 Figure 7. S-box substitution values in hexadecimal format.
var sbox = [256]byte{
0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76,
0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0,
0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15,
0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75,
0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84,
0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf,
0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8,
0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2,
0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73,
0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb,
0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79,
0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08,
0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a,
0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e,
0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf,
0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16,
}
// FIPS-197 Figure 14. Inverse S-box substitution values in hexadecimal format.
var sboxInv = [256]byte{
0x52, 0x09, 0x6a, 0xd5, 0x30, 0x36, 0xa5, 0x38, 0xbf, 0x40, 0xa3, 0x9e, 0x81, 0xf3, 0xd7, 0xfb,
0x7c, 0xe3, 0x39, 0x82, 0x9b, 0x2f, 0xff, 0x87, 0x34, 0x8e, 0x43, 0x44, 0xc4, 0xde, 0xe9, 0xcb,
0x54, 0x7b, 0x94, 0x32, 0xa6, 0xc2, 0x23, 0x3d, 0xee, 0x4c, 0x95, 0x0b, 0x42, 0xfa, 0xc3, 0x4e,
0x08, 0x2e, 0xa1, 0x66, 0x28, 0xd9, 0x24, 0xb2, 0x76, 0x5b, 0xa2, 0x49, 0x6d, 0x8b, 0xd1, 0x25,
0x72, 0xf8, 0xf6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xd4, 0xa4, 0x5c, 0xcc, 0x5d, 0x65, 0xb6, 0x92,
0x6c, 0x70, 0x48, 0x50, 0xfd, 0xed, 0xb9, 0xda, 0x5e, 0x15, 0x46, 0x57, 0xa7, 0x8d, 0x9d, 0x84,
0x90, 0xd8, 0xab, 0x00, 0x8c, 0xbc, 0xd3, 0x0a, 0xf7, 0xe4, 0x58, 0x05, 0xb8, 0xb3, 0x45, 0x06,
0xd0, 0x2c, 0x1e, 0x8f, 0xca, 0x3f, 0x0f, 0x02, 0xc1, 0xaf, 0xbd, 0x03, 0x01, 0x13, 0x8a, 0x6b,
0x3a, 0x91, 0x11, 0x41, 0x4f, 0x67, 0xdc, 0xea, 0x97, 0xf2, 0xcf, 0xce, 0xf0, 0xb4, 0xe6, 0x73,
0x96, 0xac, 0x74, 0x22, 0xe7, 0xad, 0x35, 0x85, 0xe2, 0xf9, 0x37, 0xe8, 0x1c, 0x75, 0xdf, 0x6e,
0x47, 0xf1, 0x1a, 0x71, 0x1d, 0x29, 0xc5, 0x89, 0x6f, 0xb7, 0x62, 0x0e, 0xaa, 0x18, 0xbe, 0x1b,
0xfc, 0x56, 0x3e, 0x4b, 0xc6, 0xd2, 0x79, 0x20, 0x9a, 0xdb, 0xc0, 0xfe, 0x78, 0xcd, 0x5a, 0xf4,
0x1f, 0xdd, 0xa8, 0x33, 0x88, 0x07, 0xc7, 0x31, 0xb1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xec, 0x5f,
0x60, 0x51, 0x7f, 0xa9, 0x19, 0xb5, 0x4a, 0x0d, 0x2d, 0xe5, 0x7a, 0x9f, 0x93, 0xc9, 0x9c, 0xef,
0xa0, 0xe0, 0x3b, 0x4d, 0xae, 0x2a, 0xf5, 0xb0, 0xc8, 0xeb, 0xbb, 0x3c, 0x83, 0x53, 0x99, 0x61,
0x17, 0x2b, 0x04, 0x7e, 0xba, 0x77, 0xd6, 0x26, 0xe1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0c, 0x7d,
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment