# ARC4 vector creation

This page documents the code that was used to generate the ARC4 test vectors for key lengths not available in RFC 6229. All the vectors were generated using OpenSSL and verified with Go.

## Creation

`cryptography` was modified to support ARC4 key lengths not listed in RFC 6229. 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 import ciphers
    from cryptography.hazmat.primitives.ciphers import algorithms

    _RFC6229_KEY_MATERIALS = [
        (
            True,
            8 * "0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20",
        ),
        (
            False,
            8 * "1ada31d5cf688221c109163908ebe51debb46227c6cc8b37641910833222772a",
        ),
    ]

    _RFC6229_OFFSETS = [
        0,
        16,
        240,
        256,
        496,
        512,
        752,
        768,
        1008,
        1024,
        1520,
        1536,
        2032,
        2048,
        3056,
        3072,
        4080,
        4096,
    ]

    _SIZES_TO_GENERATE = [160]

    def _key_for_size(size, keyinfo):
        msb, key = keyinfo
        if msb:
            return key[: size // 4]
        else:
            return key[-size // 4 :]

    def _build_vectors():
        count = 0
        output = []
        key = None
        plaintext = binascii.unhexlify(32 * "0")
        for size in _SIZES_TO_GENERATE:
            for keyinfo in _RFC6229_KEY_MATERIALS:
                key = _key_for_size(size, keyinfo)
                cipher = ciphers.Cipher(
                    algorithms.ARC4(binascii.unhexlify(key)),
                    None,
                )
                encryptor = cipher.encryptor()
                current_offset = 0
                for offset in _RFC6229_OFFSETS:
                    if offset % 16 != 0:
                        raise ValueError(
                            "Offset {} is not evenly divisible by 16".format(
                                offset
                            )
                        )
                    while current_offset < offset:
                        encryptor.update(plaintext)
                        current_offset += len(plaintext)
                    output.append(f"\nCOUNT = {count}")
                    count += 1
                    output.append(f"KEY = {key}")
                    output.append(f"OFFSET = {offset}")
                    output.append(f"PLAINTEXT = {binascii.hexlify(plaintext)}")
                    output.append(
                        "CIPHERTEXT = {}".format(
                            binascii.hexlify(encryptor.update(plaintext))
                        )
                    )
                    current_offset += len(plaintext)
                assert not encryptor.finalize()
        return "\n".join(output)

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

    if __name__ == "__main__":
        _write_file(_build_vectors(), "arc4.txt")

Download link: `generate_arc4.py`

## Verification

The following Go code was used to verify the vectors.

    package main

    import (
    	"bufio"
    	"bytes"
    	"crypto/rc4"
    	"encoding/hex"
    	"fmt"
    	"os"
    	"strconv"
    	"strings"
    )

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

    type vectorArgs struct {
    	count      string
    	offset     uint64
    	key        string
    	plaintext  string
    	ciphertext string
    }

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

    type arc4Verifier struct{}

    func (o arc4Verifier) validate(count string, offset uint64, key, plaintext, expectedCiphertext []byte) {
    	if offset%16 != 0 || len(plaintext) != 16 || len(expectedCiphertext) != 16 {
    		panic(fmt.Errorf("Unexpected input value encountered: offset=%v; len(plaintext)=%v; len(expectedCiphertext)=%v",
    			offset,
    			len(plaintext),
    			len(expectedCiphertext)))
    	}
    	stream, err := rc4.NewCipher(key)
    	if err != nil {
    		panic(err)
    	}

    	var currentOffset uint64 = 0
    	ciphertext := make([]byte, len(plaintext))
    	for currentOffset <= offset {
    		stream.XORKeyStream(ciphertext, plaintext)
    		currentOffset += uint64(len(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,
    					vector.offset,
    					unhexlify(vector.key),
    					unhexlify(vector.plaintext),
    					unhexlify(vector.ciphertext))
    			}
    			vector = &vectorArgs{count: segments[1]}
    		case strings.ToUpper(segments[0]) == "OFFSET":
    			vector.offset, err = strconv.ParseUint(segments[1], 10, 64)
    			if err != nil {
    				panic(err)
    			}
    		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]
    		}
    	}
    	if vector != nil {
    		verifier.validate(vector.count,
    			vector.offset,
    			unhexlify(vector.key),
    			unhexlify(vector.plaintext),
    			unhexlify(vector.ciphertext))
    	}
    }

    func main() {
    	validateVectors(arc4Verifier{}, "vectors/cryptography_vectors/ciphers/ARC4/arc4.txt")
    	fmt.Println("ARC4 OK.")
    }

Download link: `verify_arc4.go`