# 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.backends import default_backend
    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)),
            default_backend(),
        )
        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, "r") as vector_file:
            for line in vector_file:
                line = line.strip()
                if line.startswith("KEY"):
                    if count != 0:
                        output.append(
                            "CIPHERTEXT = {}".format(
                                encrypt(mode, key, iv, plaintext)
                            )
                        )
                    output.append("\nCOUNT = {}".format(count))
                    count += 1
                    name, key = line.split(" = ")
                    output.append("KEY = {}".format(key))
                elif line.startswith("IV"):
                    name, iv = line.split(" = ")
                    iv = iv[0:16]
                    output.append("IV = {}".format(iv))
                elif line.startswith("PLAINTEXT"):
                    name, plaintext = line.split(" = ")
                    output.append("PLAINTEXT = {}".format(plaintext))
            output.append(
                "CIPHERTEXT = {}".format(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`