CAST5 vector creation

This page documents the code that was used to generate the CAST5 CBC, CFB, OFB, and CTR test vectors as well as the code used to verify them against another implementation. The CBC, CFB, and OFB vectors were generated using OpenSSL and the CTR vectors were generated using Apple’s CommonCrypto. All the generated vectors were verified with Go.

Creation

cryptography was modified to support CAST5 in CBC, CFB, and OFB modes. Then the following Python script was run to generate the vector files.

# This file is dual licensed under the terms of the Apache License, Version
# 2.0, and the BSD License. See the LICENSE file in the root of this repository
# for complete details.

import binascii

from cryptography.hazmat.primitives.ciphers import algorithms, base, modes


def encrypt(mode, key, iv, plaintext):
    cipher = base.Cipher(
        algorithms.CAST5(binascii.unhexlify(key)),
        mode(binascii.unhexlify(iv)),
    )
    encryptor = cipher.encryptor()
    ct = encryptor.update(binascii.unhexlify(plaintext))
    ct += encryptor.finalize()
    return binascii.hexlify(ct)


def build_vectors(mode, filename):
    count = 0
    output = []
    key = None
    iv = None
    plaintext = None

    with open(filename) as vector_file:
        for line in vector_file:
            line = line.strip()
            if line.startswith("KEY"):
                if count != 0:
                    output.append(
                        f"CIPHERTEXT = {encrypt(mode, key, iv, plaintext)}"
                    )
                output.append(f"\nCOUNT = {count}")
                count += 1
                _, key = line.split(" = ")
                output.append(f"KEY = {key}")
            elif line.startswith("IV"):
                _, iv = line.split(" = ")
                iv = iv[0:16]
                output.append(f"IV = {iv}")
            elif line.startswith("PLAINTEXT"):
                _, plaintext = line.split(" = ")
                output.append(f"PLAINTEXT = {plaintext}")
        output.append(f"CIPHERTEXT = {encrypt(mode, key, iv, plaintext)}")
    return "\n".join(output)


def write_file(data, filename):
    with open(filename, "w") as f:
        f.write(data)


cbc_path = "tests/hazmat/primitives/vectors/ciphers/AES/CBC/CBCMMT128.rsp"
write_file(build_vectors(modes.CBC, cbc_path), "cast5-cbc.txt")
ofb_path = "tests/hazmat/primitives/vectors/ciphers/AES/OFB/OFBMMT128.rsp"
write_file(build_vectors(modes.OFB, ofb_path), "cast5-ofb.txt")
cfb_path = "tests/hazmat/primitives/vectors/ciphers/AES/CFB/CFB128MMT128.rsp"
write_file(build_vectors(modes.CFB, cfb_path), "cast5-cfb.txt")
ctr_path = "tests/hazmat/primitives/vectors/ciphers/AES/CTR/aes-128-ctr.txt"
write_file(build_vectors(modes.CTR, ctr_path), "cast5-ctr.txt")

Download link: generate_cast5.py

Verification

The following Go code was used to verify the vectors.

package main

import (
	"bufio"
	"bytes"
	"golang.org/x/crypto/cast5"
	"crypto/cipher"
	"encoding/hex"
	"fmt"
	"os"
	"strings"
)

func unhexlify(s string) []byte {
	bytes, err := hex.DecodeString(s)
	if err != nil {
		panic(err)
	}
	return bytes
}

type vectorArgs struct {
	count      string
	key        string
	iv         string
	plaintext  string
	ciphertext string
}

type vectorVerifier interface {
	validate(count string, key, iv, plaintext, expectedCiphertext []byte)
}

type ofbVerifier struct{}

func (o ofbVerifier) validate(count string, key, iv, plaintext, expectedCiphertext []byte) {
	block, err := cast5.NewCipher(key)
	if err != nil {
		panic(err)
	}

	ciphertext := make([]byte, len(plaintext))
	stream := cipher.NewOFB(block, iv)
	stream.XORKeyStream(ciphertext, plaintext)

	if !bytes.Equal(ciphertext, expectedCiphertext) {
		panic(fmt.Errorf("vector mismatch @ COUNT = %s:\n  %s != %s\n",
			count,
			hex.EncodeToString(expectedCiphertext),
			hex.EncodeToString(ciphertext)))
	}
}

type cbcVerifier struct{}

func (o cbcVerifier) validate(count string, key, iv, plaintext, expectedCiphertext []byte) {
	block, err := cast5.NewCipher(key)
	if err != nil {
		panic(err)
	}

	ciphertext := make([]byte, len(plaintext))
	mode := cipher.NewCBCEncrypter(block, iv)
	mode.CryptBlocks(ciphertext, plaintext)

	if !bytes.Equal(ciphertext, expectedCiphertext) {
		panic(fmt.Errorf("vector mismatch @ COUNT = %s:\n  %s != %s\n",
			count,
			hex.EncodeToString(expectedCiphertext),
			hex.EncodeToString(ciphertext)))
	}
}

type cfbVerifier struct{}

func (o cfbVerifier) validate(count string, key, iv, plaintext, expectedCiphertext []byte) {
	block, err := cast5.NewCipher(key)
	if err != nil {
		panic(err)
	}

	ciphertext := make([]byte, len(plaintext))
	stream := cipher.NewCFBEncrypter(block, iv)
	stream.XORKeyStream(ciphertext, plaintext)

	if !bytes.Equal(ciphertext, expectedCiphertext) {
		panic(fmt.Errorf("vector mismatch @ COUNT = %s:\n  %s != %s\n",
			count,
			hex.EncodeToString(expectedCiphertext),
			hex.EncodeToString(ciphertext)))
	}
}

type ctrVerifier struct{}

func (o ctrVerifier) validate(count string, key, iv, plaintext, expectedCiphertext []byte) {
	block, err := cast5.NewCipher(key)
	if err != nil {
		panic(err)
	}

	ciphertext := make([]byte, len(plaintext))
	stream := cipher.NewCTR(block, iv)
	stream.XORKeyStream(ciphertext, plaintext)

	if !bytes.Equal(ciphertext, expectedCiphertext) {
		panic(fmt.Errorf("vector mismatch @ COUNT = %s:\n  %s != %s\n",
			count,
			hex.EncodeToString(expectedCiphertext),
			hex.EncodeToString(ciphertext)))
	}
}

func validateVectors(verifier vectorVerifier, filename string) {
	vectors, err := os.Open(filename)
	if err != nil {
		panic(err)
	}
	defer vectors.Close()

	var segments []string
	var vector *vectorArgs

	scanner := bufio.NewScanner(vectors)
	for scanner.Scan() {
		segments = strings.Split(scanner.Text(), " = ")

		switch {
		case strings.ToUpper(segments[0]) == "COUNT":
			if vector != nil {
				verifier.validate(vector.count,
					unhexlify(vector.key),
					unhexlify(vector.iv),
					unhexlify(vector.plaintext),
					unhexlify(vector.ciphertext))
			}
			vector = &vectorArgs{count: segments[1]}
		case strings.ToUpper(segments[0]) == "IV":
			vector.iv = segments[1][:16]
		case strings.ToUpper(segments[0]) == "KEY":
			vector.key = segments[1]
		case strings.ToUpper(segments[0]) == "PLAINTEXT":
			vector.plaintext = segments[1]
		case strings.ToUpper(segments[0]) == "CIPHERTEXT":
			vector.ciphertext = segments[1]
		}
	}

}

func main() {
	validateVectors(ofbVerifier{},
		"vectors/cryptography_vectors/ciphers/CAST5/cast5-ofb.txt")
	fmt.Println("OFB OK.")
	validateVectors(cfbVerifier{},
		"vectors/cryptography_vectors/ciphers/CAST5/cast5-cfb.txt")
	fmt.Println("CFB OK.")
	validateVectors(cbcVerifier{},
		"vectors/cryptography_vectors/ciphers/CAST5/cast5-cbc.txt")
	fmt.Println("CBC OK.")
	validateVectors(ctrVerifier{},
		"vectors/cryptography_vectors/ciphers/CAST5/cast5-ctr.txt")
	fmt.Println("CTR OK.")
}

Download link: verify_cast5.go