Golang 實作 AES ECB 與 PKCS#7 PKCS#5

最近因為工作需要需要用到 AES ECB 雖然知道它算是一個比較不安全的加密方法,不過因為目前的產品其實是在一個運算量很小的硬體上面跑因此沒辦法使用運算量太大的加密演算法 Server 部分也只能配合了,不過我相信應該也是會有人遇到跟我依樣的問題,所以就寫了這篇記錄一下!

電子密碼本 (ECB)

要製作之前先來看一下 Wiki 怎麼介紹電子密碼本 (ECB) 的吧!基本上可以看到就是把 Plaintext 與 Key 放入 Block Cipher Encryption 得到 Ciphertext 再把它串起來

https://zh.wikipedia.org/wiki/File:Ecb_decryption.png

可是這個時候會遇到一個問題是資料長度不會永遠與 blockSize 成倍數,所以不足 blockSize 的時候就需要填充 (Padding) 讓資料滿足 blockSize 使用的方法有 PKCS#5 與 PKCS#7

AES 128 ECB 使用 PKCS#7

以 AES 128 ECB 來說它的 blockSize 就是 128 / 8 = 16 bytes,使用 PKCS#7 範圍就是 1 ~ 16 bytes 填充方法是拿目前的資料長度看需要滿足多少才可以是 blockSize 的倍數來決定長度與資料,所以 blockSize 為 8 作為範例只有一個字元就是 8 – 1 = 7 所以需要填充的長度就是 7 資料是 0x07,如果不太清楚可以看看以下的範例應該可以更清楚的理解要怎麼製作填充:

  • H <0x07> <0x07> <0x07> <0x07> <0x07> <0x07> <0x07>
  • He <0x06> <0x06> <0x06> <0x06> <0x06> <0x06>
  • Hel <0x05> <0x05> <0x05> <0x05> <0x05>
  • Hell <0x04> <0x04> <0x04> <0x04>
  • Hello <0x03> <0x03> <0x03>
  • HelloW <0x02> <0x02>
  • HelloWo <0x01>
  • HelloWor <0x08> <0x08> <0x08> <0x08> <0x08> <0x08> <0x08> <0x08>
  • HelloWorl <0x07> <0x07> <0x07> <0x07> <0x07> <0x07> <0x07>
  • HelloWorld <0x06> <0x06> <0x06> <0x06> <0x06> <0x06>

以 blockSize 為 8 作為範例

這邊要注意如果資料長度剛好等於 8 需要填充的資料長度是 8 bytes 而不是 0 要注意一下!

那如果是 AES 256 ECB 它的 blockSize 就是 256 / 8 = 32 bytes 依此類推

知道方法後我們就可以在 Go 上面實作看看

func PKCS7Padding(ciphertext []byte, blockSize int) []byte {
	padding := blockSize - len(ciphertext)%blockSize
	padtext := bytes.Repeat([]byte{byte(padding)}, padding)
	return append(ciphertext, padtext...)
}

PKCS#5

PKCS#5 的擴充是 PKCS#7 的子集基本上是一樣的,不同的地方在於 blockSize 固定成 8 bytes,所以最後會被切成 8 bytes 來計算填充長度,而 PKCS#7 的 blockSize 是 1 ~ 255 bytes

AES 128 ECB 加密

如上面所說 ECB 加密方法在 Golang 實作只要使用 block.Encrypt 對每個 block 做運算再把它串起來就完成了其實不難完成

func encrypt(plaintext, key []byte) []byte {
	block, err := aes.NewCipher(key)
	if err != nil {
		panic(err.Error())
	}
	data := PKCS7Padding(plaintext, block.BlockSize())
	ciphertext := make([]byte, len(data))
	size := block.BlockSize()

	for bs, be := 0, size; bs < len(data); bs, be = bs+size, be+size {
		block.Encrypt(ciphertext[bs:be], data[bs:be])
	}

	return ciphertext
}

Golang AES 128 ECB 加解密範例

基本上加解密的方法也就是一個反向就可以完成了,完成範例如下:

如果要使用也可以直接使用 Go Playground

package main

import (
	"bytes"
	"crypto/aes"
	"fmt"
)

func PKCS7Padding(ciphertext []byte, blockSize int) []byte {
	padding := blockSize - len(ciphertext)%blockSize
	padtext := bytes.Repeat([]byte{byte(padding)}, padding)
	return append(ciphertext, padtext...)
}

func PKCS7UnPadding(origData []byte) []byte {
	length := len(origData)
	unpadding := int(origData[length-1])
	return origData[:(length - unpadding)]
}

func encrypt(plaintext, key []byte) []byte {
	block, err := aes.NewCipher(key)
	if err != nil {
		panic(err.Error())
	}
	data := PKCS7Padding(plaintext, block.BlockSize())
	ciphertext := make([]byte, len(data))
	size := block.BlockSize()

	for bs, be := 0, size; bs < len(data); bs, be = bs+size, be+size {
		block.Encrypt(ciphertext[bs:be], data[bs:be])
	}

	return ciphertext
}

func decrypt(ciphertext, key []byte) []byte {
	block, err := aes.NewCipher(key)
	if err != nil {
		panic(err.Error())
	}
	decrypted := make([]byte, len(ciphertext))
	size := block.BlockSize()

	for bs, be := 0, size; bs < len(ciphertext); bs, be = bs+size, be+size {
		block.Decrypt(decrypted[bs:be], ciphertext[bs:be])
	}

	plaintext := PKCS7UnPadding(decrypted)

	return plaintext
}

func main() {
	fmt.Println("AES encryption with AES ECB PKCS7")
	plaintext := []byte("Some plain text")
	key := []byte("secretkey16bytes")

	ciphertext := encrypt(plaintext, key)
	fmt.Printf("Ciphertext: %x\n", ciphertext)
	recovered := decrypt(ciphertext, key)
	fmt.Printf("Recovered plaintext: %s\n", recovered)
}

參考資料