Click the blue “Go Language Chinese Network” above to follow, and learn Go together every day
1. What is the TEA Encryption Algorithm
TEA (Tiny Encryption Algorithm) is a simple and efficient encryption algorithm, known for its fast encryption and decryption speed and simple and efficient implementation.
This article contains a lot of code; if you are particularly concerned about practicality, please refer directly to the last chapter (Advantages and Use Cases of the TEA Encryption Algorithm
).
In the field of security, TEA (Tiny Encryption Algorithm) is a block cipher algorithm that is very simple to implement, usually requiring only a few lines of code. The TEA algorithm was originally designed by David Wheeler and Roger Needham at the Cambridge Computer Laboratory in 1994.
The TEA algorithm uses 64-bit plaintext blocks and a 128-bit key. It employs a Feistel network structure and requires 64 rounds of iteration, although the authors believe that 32 rounds are sufficient. The algorithm uses a mysterious constant δ as a multiplier, derived from the golden ratio, to ensure that each round of encryption is different. However, the exact value of δ seems to be unimportant; here TEA defines it as δ = “(√5 – 1)231” (which is 0×9E3779B9 in the program).
1.1 Algorithm Principle
Both encryption and decryption in the TEA algorithm use a constant value of 0x9e3779b, which is approximately the golden ratio. Note that some programmers avoid directly using “mov variable, 0x9e3779b” in the program to prevent attackers from easily searching for the constant 0x9e3779b to deduce the use of the TEA algorithm, so they sometimes use “sub variable, 0x61C88647” instead of “mov variable, 0x9e3779b”, where 0x61C88647 = −(0x9e3779b). The TEA algorithm can operate on 64 bits (8 bytes) at a time, using a 128-bit (16-byte) key, and the recommended number of iterations is 64 rounds, with a minimum of 32 rounds.
2. XTEA (TEAN) Algorithm
XTEA – the first version of the successor to TEA,
Later, flaws were discovered in the TEA algorithm, prompting the designers to propose an upgraded version of TEA – XTEA (sometimes referred to as “tean”). XTEA uses the same simple operations as TEA but employs a completely different order to prevent key table attacks. Four subkeys (during encryption, the original 128-bit key is split into four 32-bit subkeys) are mixed in a less conventional manner, but it is slower.
The third version of TEA, XXTEA, was published in 1998 and further improved the security of the TEA algorithm.
3. TEA Algorithm Golang Code
import (
"crypto/cipher"
"encoding/binary"
"errors"
)
const (
// TEA BlockSize in bytes
BlockSize = 8
// KeySize TEA algorithm key length in bytes.
KeySize = 16
// delta is the TEA key schedule constant.
delta = 0x9e3779b9
// numRounds TEA algorithm round standard constant.
numRounds = 64
)
//tea TEA encryption algorithm.
type tea struct {
key [16]byte
rounds int
}
// NewCipher encryption algorithm constructor, using standard rounds, key length must be 16 bytes
func NewCipher(key []byte) (cipher.Block, error) {
return NewCipherWithRounds(key, numRounds)
}
// NewCipherWithRounds encryption algorithm constructor, key length must be 16 bytes
func NewCipherWithRounds(key []byte, rounds int) (cipher.Block, error) {
if len(key) != 16 {
return nil, errors.New("tea: incorrect key size")
}
if rounds&1 != 0 {
return nil, errors.New("tea: odd number of rounds specified")
}
c := &tea{
rounds: rounds,
}
copy(c.key[:], key)
return c, nil
}
// BlockSize returns TEA block size, result is constant. Method to satisfy package "crypto/cipher" Block interface
func (*tea) BlockSize() int {
return BlockSize
}
// Encrypt uses t.key to encrypt src parameter 8-byte buffer content, ciphertext is stored in dst.
// Note that the length of data is greater than block, calling encrypt on consecutive blocks is unsafe, should use CBC crypto/cipher/cbc.go method to encrypt
func (t *tea) Encrypt(dst, src []byte) {
e := binary.BigEndian
v0, v1 := e.Uint32(src), e.Uint32(src[4:])
k0, k1, k2, k3 := e.Uint32(t.key[0:]), e.Uint32(t.key[4:]), e.Uint32(t.key[8:]), e.Uint32(t.key[12:])
sum := uint32(0)
delta := uint32(delta)
for i := 0; i < t.rounds/2; i++ {
sum += delta
v0 += ((v1 << 4) + k0) ^ (v1 + sum) ^ ((v1 >> 5) + k1)
v1 += ((v0 << 4) + k2) ^ (v0 + sum) ^ ((v0 >> 5) + k3)
}
e.PutUint32(dst, v0)
e.PutUint32(dst[4:], v1)
}
// Decrypt uses t.key to decrypt src parameter 8-byte buffer content, plaintext is stored in dst.
func (t *tea) Decrypt(dst, src []byte) {
e := binary.BigEndian
v0, v1 := e.Uint32(src), e.Uint32(src[4:])
k0, k1, k2, k3 := e.Uint32(t.key[0:]), e.Uint32(t.key[4:]), e.Uint32(t.key[8:]), e.Uint32(t.key[12:])
delta := uint32(delta)
sum := delta * uint32(t.rounds/2) // in general, sum = delta * n
for i := 0; i < t.rounds/2; i++ {
v1 -= ((v0 << 4) + k2) ^ (v0 + sum) ^ ((v0 >> 5) + k3)
v0 -= ((v1 << 4) + k0) ^ (v1 + sum) ^ ((v1 >> 5) + k1)
sum -= delta
}
e.PutUint32(dst, v0)
e.PutUint32(dst[4:], v1)
}
4. XTEA Algorithm Golang Code
block.go
package xtea
// XTEA is based on 64 rounds.
const numRounds = 64
// blockToUint32 reads 8 bytes and outputs 2 uint32
func blockToUint32(src []byte) (uint32, uint32) {
r0 := uint32(src[0])<<24 | uint32(src[1])<<16 | uint32(src[2])<<8 | uint32(src[3])
r1 := uint32(src[4])<<24 | uint32(src[5])<<16 | uint32(src[6])<<8 | uint32(src[7])
return r0, r1
}
// uint32ToBlock outputs 2 uint32 into 8 bytes
func uint32ToBlock(v0, v1 uint32, dst []byte) {
dst[0] = byte(v0 >> 24)
dst[1] = byte(v0 >> 16)
dst[2] = byte(v0 >> 8)
dst[3] = byte(v0)
dst[4] = byte(v1 >> 24)
dst[5] = byte(v1 >> 16)
dst[6] = byte(v1 >> 8)
dst[7] = byte(v1 >> 0)
}
// encryptBlock encrypts a single 8-byte block using XTEA
func encryptBlock(c *Cipher, dst, src []byte) {
v0, v1 := blockToUint32(src)
// Two rounds of XTEA applied per loop
for i := 0; i < numRounds; {
v0 += ((v1<<4 ^ v1>>5) + v1) ^ c.table[i]
i++
v1 += ((v0<<4 ^ v0>>5) + v0) ^ c.table[i]
i++
}
uint32ToBlock(v0, v1, dst)
}
// decryptBlock decrypts a single 8-byte block using XTEA
func decryptBlock(c *Cipher, dst, src []byte) {
v0, v1 := blockToUint32(src)
// Two rounds of XTEA applied per loop
for i := numRounds; i > 0; {
i--
v1 -= ((v0<<4 ^ v0>>5) + v0) ^ c.table[i]
i--
v0 -= ((v1<<4 ^ v1>>5) + v1) ^ c.table[i]
}
uint32ToBlock(v0, v1, dst)
}
cipher.go
package xtea
import "strconv"
// XTEA block size in bytes.
const BlockSize = 8
// A Cipher is an instance of an XTEA cipher using a particular key.
type Cipher struct {
// table contains a series of precalculated values that are used each round.
table [64]uint32
}
// KeySizeError custom error
type KeySizeError int
// Error .
func (k KeySizeError) Error() string {
return "crypto/xtea: invalid key size " + strconv.Itoa(int(k))
}
// NewCipher constructor.
// key must be 16 bytes long.
func NewCipher(key []byte) (*Cipher, error) {
k := len(key)
switch k {
default:
return nil, KeySizeError(k)
case 16:
break
}
c := new(Cipher)
initCipher(c, key)
return c, nil
}
// BlockSize returns XTEA block size, result is constant. Method to satisfy package "crypto/cipher" Block interface
func (c *Cipher) BlockSize() int { return BlockSize }
// Encrypt encrypts src parameter 8-byte buffer content, plaintext is stored in dst.
// Note that the length of data is greater than block, calling encrypt on consecutive blocks is unsafe, should use CBC crypto/cipher/cbc.go method to encrypt
func (c *Cipher) Encrypt(dst, src []byte) { encryptBlock(c, dst, src) }
// Decrypt decrypts the 8-byte buffer src using the key and stores the result in dst.
// Decrypt uses t.key to decrypt src parameter 8-byte buffer content, plaintext is stored in dst.
func (c *Cipher) Decrypt(dst, src []byte) { decryptBlock(c, dst, src) }
// initCipher converts the key into a precalculated table
func initCipher(c *Cipher, key []byte) {
// Load the key into four uint32s
var k [4]uint32
for i := 0; i < len(k); i++ {
j := i << 2 // Multiply by 4
k[i] = uint32(key[j+0])<<24 | uint32(key[j+1])<<16 | uint32(key[j+2])<<8 | uint32(key[j+3])
}
// Precalculate the table
const delta = 0x9E3779B9
var sum uint32
// Two rounds of XTEA applied per loop
for i := 0; i < numRounds; {
c.table[i] = sum + k[sum&3]
i++
sum += delta
c.table[i] = sum + k[(sum>>11)&3]
i++
}
}
5. Advantages and Use Cases of the TEA Encryption Algorithm
TEA (Tiny Encryption Algorithm) is a simple and efficient encryption algorithm, known for its fast encryption and decryption speed and simple and efficient implementation. The algorithm is indeed very simple; the TEA algorithm can operate on 64 bits (8 bytes) at a time, using a 128-bit (16-byte) key, and the recommended number of iterations is 64 rounds, with a minimum of 32 rounds.
5.1 Basic Unit Tests
import (
"bytes"
"crypto/cipher"
"crypto/rand"
"encoding/base64"
"errors"
"fmt"
"golang.org/x/crypto/tea"
"golang.org/x/crypto/xtea"
"io"
"testing"
)
func TestTeaDemo(t *testing.T) {
key := []byte("mojotv.cn.=.good")// length must be 16 bytes
c, err := tea.NewCipherWithRounds(key, 8)
if err != nil {
t.Fatal(err)
}
raw := []byte("mojotvcn")// length must be 8 bytes
dst := make([]byte,8)// length must be 8 bytes
c.Encrypt(dst,raw)
raw2 := make([]byte,8)// length must be 8 bytes
c.Decrypt(raw2,dst[:])
if !bytes.Equal(raw,raw2){
t.Error("Failed")
}
t.Log("Validation successful")
}
func TestXteaDemo(t *testing.T) {
key := []byte("mojotv.cn.=.good")// length must be 16 bytes
c, err := xtea.NewCipher(key)
if err != nil {
t.Fatal(err)
}
raw := []byte("mojotvcn")// length must be 8 bytes
dst := make([]byte,8)// length must be 8 bytes
c.Encrypt(dst,raw)
raw2 := make([]byte,8)// length must be 8 bytes
c.Decrypt(raw2,dst[:])
if !bytes.Equal(raw,raw2){
t.Error("Failed")
}
t.Log("XTEA validation successful")
}
5.2 Using XTEA with CBC Base64Url Encoding
The TEA encryption algorithm can be slightly modified with the following code. mojocn/alg-tea[1]
// Use PKCS7 for padding
func pKCS7Padding(ciphertext []byte, blockSize int) []byte {
padding := blockSize - len(ciphertext) % blockSize
padtext := bytes.Repeat([]byte{byte(padding)}, padding)
return append(ciphertext, padtext...)
}
// Use PKCS7 for padding restoration
func pKCS7UnPadding(origData []byte) []byte {
length := len(origData)
unpadding := int(origData[length-1])
return origData[:(length - unpadding)]
}
// XteaCbcEncrypt key length must be 16 bytes
func XteaCbcEncrypt(rawData,key []byte) ([]byte, error) {
block, err := xtea.NewCipher(key)
if err != nil {
return nil,fmt.Errorf("key must be 16 bytes, %v",err)
}
// Pad the plaintext
blockSize := block.BlockSize()
rawData = pKCS7Padding(rawData, blockSize)
// The initialization vector IV must be unique but does not need to be kept secret
cipherText := make([]byte,blockSize+len(rawData))
// block size 16
iv := cipherText[:blockSize]
if _, err := io.ReadFull(rand.Reader,iv); err != nil {
panic(err)
}
// The block size and initialization vector size must be the same
mode := cipher.NewCBCEncrypter(block,iv)
mode.CryptBlocks(cipherText[blockSize:],rawData)
return cipherText, nil
}
// XteaCbcDecrypt key length must be 16 bytes
func XteaCbcDecrypt(encryptData, key []byte) ([]byte,error) {
block, err := xtea.NewCipher(key)
if err != nil {
return nil,fmt.Errorf("key must be 16 bytes, %v",err)
}
blockSize := block.BlockSize()
if len(encryptData) < blockSize {
return nil,errors.New("ciphertext too short")
}
iv := encryptData[:blockSize]
encryptData = encryptData[blockSize:]
// CBC mode always works in whole blocks.
if len(encryptData)%blockSize != 0 {
return nil,errors.New("ciphertext is not a multiple of the block size")
}
mode := cipher.NewCBCDecrypter(block, iv)
// CryptBlocks can work in-place if the two arguments are the same.
mode.CryptBlocks(encryptData, encryptData)
// Unpadding
encryptData = pKCS7UnPadding(encryptData)
return encryptData,nil
}
func XteaB64urlEncrypt(rawData,key []byte) (string,error) {
data, err:= XteaCbcEncrypt(rawData,key)
if err != nil {
return "",err
}
return base64.RawURLEncoding.EncodeToString(data),nil
}
func XteaB64urlDecrypt(rawData string,key []byte) (string,error) {
data,err := base64.RawURLEncoding.DecodeString(rawData)
if err != nil {
return "",err
}
dnData,err := XteaCbcDecrypt(data,key)
if err != nil {
return "",err
}
return string(dnData),nil
}
func TestXteaCbcB64url(t *testing.T) {
key := []byte("mojotv.cn.=.good")// length must be 16 bytes
raw := "mojotv.cn and golang are great friends" // can be any length []byte
ciper,err := XteaB64urlEncrypt([]byte(raw),key)
if err != nil {
t.Error("XTEA CBC Base64 URL encryption failed",err)
return
}
decrypt, err := XteaB64urlDecrypt(ciper, key)
if err != nil {
t.Error("XTEA CBC Base64 URL decryption failed",err)
return
}
if decrypt != raw {
t.Error("Decryption result is incorrect")
}
}
Github Action Unit Test Execution Results[2]
=== RUN TestTeaDemo
tea_test.go:32: Validation successful
--- PASS: TestTeaDemo (0.00s)
=== RUN TestXteaDemo
tea_test.go:50: XTEA validation successful
--- PASS: TestXteaDemo (0.00s)
=== RUN TestXteaCbcB64url
--- PASS: TestXteaCbcB64url (0.00s)
PASS
ok mojotv.cn/flash 0.005s
Original link: https://mojotv.cn/golang/encrypt-tea-xtea-simple-efficient
Author: Eric Zhou
References
[1]
mojocn/alg-tea: https://github.com/mojocn/alg-tea/blob/master/tea_test.go
[2]
Github Action Unit Test Execution Results: https://github.com/mojocn/alg-tea/runs/1074969486?check_suite_focus=true
Recommended Reading
-
Discussion from HackerNews: Life is short, I want to switch to Go!
BenefitsI have compiled a set of Go learning materials from beginner to advanced, including learning suggestions: what to read for beginners, what to read for advanced. Follow the public account “polarisxu”, reply ebook to get; you can also reply “join group” to communicate and learn with thousands of Gophers.