Implementing DESede/ECB/NoPadding cipher algorithm in GoLang

  sonic0002        2019-07-29 06:43:50       8,318        0    

By default, GoLang doesn't provide the ECB mode cipher for DESede though there is CBC mode provided. In cases we need to encrypt/decrypt data with ECB mode, we need to implement those by ourselves. This mode is frequently used when encrypting/decrypting PIN block which is small block data less than 16 bytes.

In this post, we will introduce how to implement the DESede/ECB/NoPadding algorithm in GoLang by using the existing cipher support. Here we will not cover how DESede works in detail, instead we will just cover the logic to make it work.

Below is the sample code which can be used to encrypt/decrypt data in DESede(3DES) ECB mode.

import (
	"bytes"
	"crypto/des"
	"errors"
)

// DESedeECBEncrypt ...
func DESedeECBEncrypt(origData, key []byte) ([]byte, error) {
	tkey := make([]byte, 24, 24)
	copy(tkey, key)
	k1 := tkey[:8]
	k2 := tkey[8:16]
	k3 := tkey[16:]

	buf1, err := encrypt(origData, k1)
	if err != nil {
		return nil, err
	}
	buf2, err := decrypt(buf1, k2)
	if err != nil {
		return nil, err
	}
	out, err := encrypt(buf2, k3)
	if err != nil {
		return nil, err
	}
	return out, nil
}

// DESedeECBDecrypt ...
func DESedeECBDecrypt(crypted, key []byte) ([]byte, error) {
	tkey := make([]byte, 24, 24)
	copy(tkey, key)
	k1 := tkey[:8]
	k2 := tkey[8:16]
	k3 := tkey[16:]
	buf1, err := decrypt(crypted, k3)
	if err != nil {
		return nil, err
	}
	buf2, err := encrypt(buf1, k2)
	if err != nil {
		return nil, err
	}
	out, err := decrypt(buf2, k1)
	if err != nil {
		return nil, err
	}
	return out, nil
}

func encrypt(origData, key []byte) ([]byte, error) {
	if len(origData) < 1 || len(key) < 1 {
		return nil, errors.New("wrong data or key")
	}
	block, err := des.NewCipher(key)
	if err != nil {
		return nil, err
	}
	bs := block.BlockSize()
	if len(origData)%bs != 0 {
		return nil, errors.New("wrong padding")
	}
	out := make([]byte, len(origData))
	dst := out
	for len(origData) > 0 {
		block.Encrypt(dst, origData[:bs])
		origData = origData[bs:]
		dst = dst[bs:]
	}
	return out, nil
}

func decrypt(crypted, key []byte) ([]byte, error) {
	if len(crypted) < 1 || len(key) < 1 {
		return nil, errors.New("wrong data or key")
	}
	block, err := des.NewCipher(key)
	if err != nil {
		return nil, err
	}
	out := make([]byte, len(crypted))
	dst := out
	bs := block.BlockSize()
	if len(crypted)%bs != 0 {
		return nil, errors.New("wrong crypted size")
	}

	for len(crypted) > 0 {
		block.Decrypt(dst, crypted[:bs])
		crypted = crypted[bs:]
		dst = dst[bs:]
	}

	return out, nil
}

Normally a key will be 24 bytes for DESede/3DES algorithm as it's basically a DES doing three times. There are three steps for encrypting the data.

  1. Get three keys from the original key, basically divide the 24 bytes into three 8 bytes key(k1, k2, k3)
  2. Use k1 to encrypt the original data
  3. Use k2 to decrypt the encrypted data
  4. Use k3 to encrypt the data again
  5. Return the output

The decryption is a reversed process with the key.

  1. Get three keys from the original key, basically divide the 24 bytes into three 8 bytes key(k1, k2, k3)
  2. Use k3 to decrypt the original data
  3. Use k2 to encrypt the decrypted data
  4. Use k1 to decrypt the data again
  5. Return the output

A sample testing looks like:

func TestENcDec(t *testing.T) {
	data := []byte{0, 1, 2, 3, 4, 5, 6, 7}
	key := make([]byte, 24)
	key[1] = 8

	encryptedData, err := DESedeECBEncrypt([]byte(data), key)
	if err != nil {
		fmt.Printf("error on encrypting data %s, err=%+v\n", data, err)
	}
	decryptedData, err := DESedeECBDecrypt(encryptedData, key)
	if err != nil {
		fmt.Printf("error on decrypting data %s, err=%+v\n", string(encryptedData), err)
	}

	fmt.Printf("original  data: %v\n", data)
	fmt.Printf("decrypted data: %v\n", decryptedData)
	assert.Equal(t, decryptedData, data)
}

After decryption, the output is

original  data: [0 1 2 3 4 5 6 7]
decrypted data: [0 1 2 3 4 5 6 7]

If padding is needed like PKCS#5 padding or Zero Padding, you can also easily implement them. Below are functions for get PKCS#5 padding data.

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

func PKCS5Unpadding(origData []byte) []byte {
    length := len(origData)
    unpadding := int(origData[length-1])
    return origData[:(length - unpadding)]
}
func ZeroPadding(ciphertext []byte, blockSize int) []byte {
	padding := blockSize - len(ciphertext)%blockSize
	padText := bytes.Repeat([]byte{0}, padding)
	return append(ciphertext, padText...)
}

func ZeroUnPadding(origData []byte) []byte {
	return bytes.TrimFunc(origData,
		func(r rune) bool {
			return r == rune(0)
		})
}

You may want to update the encryption/decryption to take consideration of padding with below:

import (
	"bytes"
	"crypto/des"
	"errors"
)

// DESedeECBEncrypt ...
func DESedeECBEncrypt(origData, key []byte) ([]byte, error) {
	tkey := make([]byte, 24, 24)
	copy(tkey, key)
	k1 := tkey[:8]
	k2 := tkey[8:16]
	k3 := tkey[16:]

	block, err := des.NewCipher(k1)
	if err != nil {
		return nil, err
	}
	bs := block.BlockSize()
	origData = zeroPadding(origData, bs)

	buf1, err := encrypt(origData, k1)
	if err != nil {
		return nil, err
	}
	buf2, err := decrypt(buf1, k2)
	if err != nil {
		return nil, err
	}
	out, err := encrypt(buf2, k3)
	if err != nil {
		return nil, err
	}
	return out, nil
}

// DESedeECBDecrypt ...
func DESedeECBDecrypt(crypted, key []byte) ([]byte, error) {
	tkey := make([]byte, 24, 24)
	copy(tkey, key)
	k1 := tkey[:8]
	k2 := tkey[8:16]
	k3 := tkey[16:]
	buf1, err := decrypt(crypted, k3)
	if err != nil {
		return nil, err
	}
	buf2, err := encrypt(buf1, k2)
	if err != nil {
		return nil, err
	}
	out, err := decrypt(buf2, k1)
	if err != nil {
		return nil, err
	}
	out = zeroUnPadding(out)
	return out, nil
}

func encrypt(origData, key []byte) ([]byte, error) {
	if len(origData) < 1 || len(key) < 1 {
		return nil, errors.New("wrong data or key")
	}
	block, err := des.NewCipher(key)
	if err != nil {
		return nil, err
	}
	bs := block.BlockSize()
	if len(origData)%bs != 0 {
		return nil, errors.New("wrong padding")
	}
	out := make([]byte, len(origData))
	dst := out
	for len(origData) > 0 {
		block.Encrypt(dst, origData[:bs])
		origData = origData[bs:]
		dst = dst[bs:]
	}
	return out, nil
}

func decrypt(crypted, key []byte) ([]byte, error) {
	if len(crypted) < 1 || len(key) < 1 {
		return nil, errors.New("wrong data or key")
	}
	block, err := des.NewCipher(key)
	if err != nil {
		return nil, err
	}
	out := make([]byte, len(crypted))
	dst := out
	bs := block.BlockSize()
	if len(crypted)%bs != 0 {
		return nil, errors.New("wrong crypted size")
	}

	for len(crypted) > 0 {
		block.Decrypt(dst, crypted[:bs])
		crypted = crypted[bs:]
		dst = dst[bs:]
	}

	return out, nil
}

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

func zeroUnPadding(origData []byte) []byte {
	return bytes.TrimFunc(origData,
		func(r rune) bool {
			return r == rune(0)
		})
}

Hope this helps those who needs to use ECB mode for DESede.

Reference: https://segmentfault.com/a/1190000006183694

GOLANG  SAMPLE  SECURITY  DES  DESEDE  3DES 

       

  RELATED


  0 COMMENT


No comment for this article.



  RANDOM FUN

Is this an Ad from Microsoft?