Created
October 31, 2024 19:07
-
-
Save psnehanshu/aebcb1435064bc443b2827bf7e747723 to your computer and use it in GitHub Desktop.
AES encryption implementation in Golang
This file contains hidden or 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
package encryption | |
import ( | |
"fmt" | |
"math" | |
) | |
// Rijndael S-box as a 256-byte array | |
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, | |
} | |
func AesEncrypt(data, iv, key []byte) ([]byte, error) { | |
if len(iv) != 16 { | |
return nil, fmt.Errorf("iv must be of length 16, got %d", len(iv)) | |
} | |
// Determine number of rounds based on KeySize | |
numOfRounds, keys, err := expandKey(key) | |
if err != nil { | |
return nil, err | |
} | |
if len(keys) != numOfRounds+1 { | |
panic(fmt.Sprintf("Recevied %d round keys for %d number of rounds", len(keys), numOfRounds)) | |
} | |
// Convert the input data into blocks of 4x4 | |
blocks := convert2blocks(data, 4) | |
// Add IV | |
for i := range blocks { | |
blocks[i] = addRoundKey(blocks[i], iv) | |
} | |
for i, block := range blocks { | |
// Perform the rounds for each block | |
for j := range numOfRounds { | |
// Add round key, only on the initial round | |
if j == 0 { | |
block = addRoundKey(block, keys[0]) | |
} | |
block = subBytes(block) | |
block = shiftRows(block) | |
// don't execute mixColumns on last round | |
if j < numOfRounds-1 { | |
block = mixColumns(block) | |
} | |
block = addRoundKey(block, keys[j+1]) | |
} | |
blocks[i] = block | |
} | |
return flattenBlocks(blocks), nil | |
} | |
func convert2blocks(data []byte, blockSideLen int) [][][]byte { | |
blockSize := blockSideLen * blockSideLen | |
// Calculate into how many 16 byte blocks the data can be divided | |
numOfBlocks := len(data) / blockSize | |
if len(data)%blockSize > 0 { | |
numOfBlocks++ | |
} | |
// reserve memory for the array of blocks | |
blocks := make([][][]byte, numOfBlocks) | |
// Looping through each blockIdx | |
for blockIdx := 0; blockIdx < numOfBlocks; blockIdx++ { | |
// Arrange in column-major grid | |
startIndex := blockIdx * blockSize | |
endIndex := min(startIndex+blockSize, len(data)) | |
bytes := data[startIndex:endIndex] | |
block := make([][]byte, blockSideLen) | |
for i := 0; i < len(bytes); i++ { | |
// initialize the row | |
rowNum := i % blockSideLen | |
colNum := i / blockSideLen | |
if block[rowNum] == nil { | |
block[rowNum] = make([]byte, blockSideLen) | |
} | |
block[rowNum][colNum] = bytes[i] | |
} | |
blocks[blockIdx] = block | |
} | |
return blocks | |
} | |
func min(nums ...int) int { | |
min := math.MaxInt | |
for _, n := range nums { | |
if n < min { | |
min = n | |
} | |
} | |
return min | |
} | |
func expandKey(key []byte) (int, [][]byte, error) { | |
// Calculate number of rounds | |
numOfRounds, err := func(keySize int) (int, error) { | |
switch keySize { | |
case 16: | |
return 10, nil | |
case 24: | |
return 12, nil | |
case 32: | |
return 14, nil | |
default: | |
return 0, fmt.Errorf("key length %d is not supported", keySize) | |
} | |
}(len(key)) | |
if err != nil { | |
return 0, nil, err | |
} | |
wordsInEachRound := len(key) / 4 | |
words := make([][]byte, (numOfRounds+1)*wordsInEachRound) | |
// Generate inital words | |
for i := 0; i < wordsInEachRound; i++ { | |
start := i * wordsInEachRound | |
end := start + wordsInEachRound | |
words[i] = key[start:end] | |
} | |
// Generate round constants | |
roundConstants := generateRcons(numOfRounds) | |
// Perform the rounds | |
for i := range numOfRounds { | |
for j := range wordsInEachRound { | |
idx := ((i + 1) * wordsInEachRound) + j | |
// if word index is multiple of key length, then perform special transformation | |
if idx%wordsInEachRound == 0 { | |
words[idx] = xorBytes(g(words[idx-1], roundConstants[i]), words[idx-wordsInEachRound]) | |
} else { | |
words[idx] = xorBytes(words[idx-1], words[idx-wordsInEachRound]) | |
} | |
} | |
} | |
// Group words into 4 to make keys | |
keys := make([][]byte, numOfRounds+1) | |
for i := range keys { | |
rkey := make([]byte, 16) | |
wordsSlice := words[i*4 : i*4+4] | |
for j := range rkey { | |
rkey[j] = wordsSlice[j/4][j%4] | |
} | |
keys[i] = rkey | |
} | |
return numOfRounds, keys, nil | |
} | |
func subBytes(block [][]byte) [][]byte { | |
for i := range block { | |
for j := range block[i] { | |
block[i][j] = substitute(block[i][j]) | |
} | |
} | |
return block | |
} | |
func shiftRows(block [][]byte) [][]byte { | |
for i := range block { | |
block[i] = shiftRow(block[i], i) | |
} | |
return block | |
} | |
func shiftRow(row []byte, extent int) []byte { | |
for i := 0; i < extent; i++ { | |
b0 := row[0] | |
for i, b := range row { | |
if i == 0 { | |
continue | |
} else { | |
row[i-1] = b | |
} | |
if i == len(row)-1 { | |
row[i] = b0 | |
} | |
} | |
} | |
return row | |
} | |
func mixColumns(block [][]byte) [][]byte { | |
for i := 0; i < 4; i++ { | |
col := make([]byte, 4) | |
for j := range block { | |
col[j] = block[j][i] | |
} | |
col = mixColumn(col) | |
for j := range block { | |
block[j][i] = col[j] | |
} | |
} | |
return block | |
} | |
// MixColumn applies the MixColumns transformation to a single column | |
func mixColumn(column []byte) []byte { | |
return []byte{ | |
gfMul(column[0], 0x02) ^ gfMul(column[1], 0x03) ^ column[2] ^ column[3], | |
column[0] ^ gfMul(column[1], 0x02) ^ gfMul(column[2], 0x03) ^ column[3], | |
column[0] ^ column[1] ^ gfMul(column[2], 0x02) ^ gfMul(column[3], 0x03), | |
gfMul(column[0], 0x03) ^ column[1] ^ column[2] ^ gfMul(column[3], 0x02), | |
} | |
} | |
func addRoundKey(block [][]byte, key []byte) [][]byte { | |
if len(key) != 16 { | |
panic(fmt.Sprintf("Key must of of 16 bytes, got %d bytes", len(key))) | |
} | |
if len(block) != 4 { | |
panic(fmt.Sprintf("block must have 4 rows, got %d", len(block))) | |
} | |
for i := range block { | |
if len(block[i]) != 4 { | |
panic(fmt.Sprintf("block must have 4 columns, got %d", len(block[i]))) | |
} | |
for j := range block[i] { | |
idx := i + j*4 | |
block[i][j] ^= key[idx] | |
} | |
} | |
return block | |
} | |
func substitute(b byte) byte { | |
return sBox[b] | |
} | |
func g(word []byte, rcon byte) []byte { | |
if len(word) != 4 { | |
panic(fmt.Sprintf("word must be of 4 bytes length, give length %d", len(word))) | |
} | |
// Shift left | |
word = shiftRow(word, 1) | |
// Substitue | |
for i := range word { | |
word[i] = substitute(word[i]) | |
} | |
// Xor with RCon | |
word[0] ^= rcon | |
return word | |
} | |
// generateRcons generates Rcon values for the specified number of rounds in AES | |
func generateRcons(rounds int) []byte { | |
rcon := make([]byte, rounds) | |
rcon[0] = 0x01 // The first Rcon value | |
for i := 1; i < rounds; i++ { | |
// Double the previous Rcon value | |
rcon[i] = rcon[i-1] << 1 | |
// If the value is greater than 0xFF, apply modular reduction | |
if rcon[i] > 0xFF { | |
rcon[i] ^= 0x1b // XOR with 0x1b (0x11b minus 0x100) for reduction in GF(2^8) | |
} | |
} | |
return rcon | |
} | |
func xorBytes(b1, b2 []byte) []byte { | |
if len(b1) != len(b2) { | |
panic(fmt.Sprintf("When xor'ing two array of bytes, both arrays should be of the same length. Got lengths: (%d, %d)", len(b1), len(b2))) | |
} | |
res := make([]byte, len(b1)) | |
for i := range b1 { | |
res[i] = b1[i] ^ b2[i] | |
} | |
return res | |
} | |
// Galois Field Multiply a byte by 2 in GF(2^8) | |
func gfMul(a byte, multiplier byte) byte { | |
switch multiplier { | |
case 0x01: | |
return a | |
case 0x02: | |
if a&0x80 != 0 { | |
return (a << 1) ^ 0x1b | |
} | |
return a << 1 | |
case 0x03: | |
return gfMul(a, 0x02) ^ a | |
} | |
return 0 | |
} | |
func flattenBlocks(blocks [][][]byte) []byte { | |
var combined []byte | |
for _, block := range blocks { | |
for _, word := range block { | |
combined = append(combined, word...) // Flatten each word into the combined array | |
} | |
} | |
return combined | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Note: It doesn't work :(