diff options
Diffstat (limited to 'docs/development/custom-vectors')
18 files changed, 1007 insertions, 27 deletions
diff --git a/docs/development/custom-vectors/arc4.rst b/docs/development/custom-vectors/arc4.rst new file mode 100644 index 00000000..5b1b65d5 --- /dev/null +++ b/docs/development/custom-vectors/arc4.rst @@ -0,0 +1,30 @@ +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. + +.. literalinclude:: /development/custom-vectors/arc4/generate_arc4.py + +Download link: :download:`generate_arc4.py +</development/custom-vectors/arc4/generate_arc4.py>` + + +Verification +------------ + +The following Go code was used to verify the vectors. + +.. literalinclude:: /development/custom-vectors/arc4/verify_arc4.go + :language: go + +Download link: :download:`verify_arc4.go +</development/custom-vectors/arc4/verify_arc4.go>` diff --git a/docs/development/custom-vectors/arc4/generate_arc4.py b/docs/development/custom-vectors/arc4/generate_arc4.py new file mode 100644 index 00000000..3dee44a3 --- /dev/null +++ b/docs/development/custom-vectors/arc4/generate_arc4.py @@ -0,0 +1,98 @@ +# 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. + +from __future__ import absolute_import, division, print_function + +import binascii + +from cryptography.hazmat.backends import default_backend +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, + default_backend()) + 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("\nCOUNT = {}".format(count)) + count += 1 + output.append("KEY = {}".format(key)) + output.append("OFFSET = {}".format(offset)) + output.append("PLAINTEXT = {}".format( + 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') diff --git a/docs/development/custom-vectors/arc4/verify_arc4.go b/docs/development/custom-vectors/arc4/verify_arc4.go new file mode 100644 index 00000000..508fe980 --- /dev/null +++ b/docs/development/custom-vectors/arc4/verify_arc4.go @@ -0,0 +1,111 @@ +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.") +} diff --git a/docs/development/custom-vectors/cast5/generate_cast5.py b/docs/development/custom-vectors/cast5/generate_cast5.py index 20f35179..ce046b0f 100644 --- a/docs/development/custom-vectors/cast5/generate_cast5.py +++ b/docs/development/custom-vectors/cast5/generate_cast5.py @@ -23,33 +23,34 @@ def encrypt(mode, key, iv, plaintext): def build_vectors(mode, filename): - vector_file = open(filename, "r") - count = 0 output = [] key = None iv = None plaintext = None - 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))) + 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) @@ -57,6 +58,7 @@ 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" diff --git a/docs/development/custom-vectors/cast5/verify_cast5.go b/docs/development/custom-vectors/cast5/verify_cast5.go index cbc89a03..b2d711a9 100644 --- a/docs/development/custom-vectors/cast5/verify_cast5.go +++ b/docs/development/custom-vectors/cast5/verify_cast5.go @@ -3,7 +3,7 @@ package main import ( "bufio" "bytes" - "code.google.com/p/go.crypto/cast5" + "golang.org/x/crypto/cast5" "crypto/cipher" "encoding/hex" "fmt" diff --git a/docs/development/custom-vectors/hkdf.rst b/docs/development/custom-vectors/hkdf.rst new file mode 100644 index 00000000..6d7e8618 --- /dev/null +++ b/docs/development/custom-vectors/hkdf.rst @@ -0,0 +1,28 @@ +HKDF vector creation +==================== + +This page documents the code that was used to generate a longer +HKDF test vector (1200 bytes) than is available in :rfc:`5869`. All +the vectors were generated using OpenSSL and verified with Go. + +Creation +-------- + +The following Python script was run to generate the vector files. + +.. literalinclude:: /development/custom-vectors/hkdf/generate_hkdf.py + +Download link: :download:`generate_hkdf.py +</development/custom-vectors/hkdf/generate_hkdf.py>` + + +Verification +------------ + +The following Go code was used to verify the vectors. + +.. literalinclude:: /development/custom-vectors/hkdf/verify_hkdf.go + :language: go + +Download link: :download:`verify_hkdf.go +</development/custom-vectors/hkdf/verify_hkdf.go>` diff --git a/docs/development/custom-vectors/hkdf/generate_hkdf.py b/docs/development/custom-vectors/hkdf/generate_hkdf.py new file mode 100644 index 00000000..8976effd --- /dev/null +++ b/docs/development/custom-vectors/hkdf/generate_hkdf.py @@ -0,0 +1,39 @@ +# 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. + +from __future__ import absolute_import, division, print_function + +import binascii + +from cryptography.hazmat.backends import default_backend +from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.primitives.kdf.hkdf import HKDF + +IKM = binascii.unhexlify(b"0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b") +L = 1200 +OKM = HKDF( + algorithm=hashes.SHA256(), length=L, salt=None, info=None, + backend=default_backend() +).derive(IKM) + + +def _build_vectors(): + output = [ + "COUNT = 0", + "Hash = SHA-256", + "IKM = " + binascii.hexlify(IKM).decode("ascii"), + "salt = ", "info = ", + "L = {}".format(L), + "OKM = " + binascii.hexlify(OKM).decode("ascii"), + ] + 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(), 'hkdf.txt') diff --git a/docs/development/custom-vectors/hkdf/verify_hkdf.go b/docs/development/custom-vectors/hkdf/verify_hkdf.go new file mode 100644 index 00000000..ddeb3d8e --- /dev/null +++ b/docs/development/custom-vectors/hkdf/verify_hkdf.go @@ -0,0 +1,69 @@ +package main + +import ( + "bufio" + "bytes" + "crypto/sha256" + "encoding/hex" + "fmt" + "golang.org/x/crypto/hkdf" + "io" + "os" + "strconv" + "strings" +) + +func unhexlify(s string) []byte { + bytes, err := hex.DecodeString(s) + if err != nil { + panic(err) + } + return bytes +} + +func verifier(l uint64, ikm, okm []byte) bool { + hash := sha256.New + hkdf := hkdf.New(hash, ikm, nil, nil) + okmComputed := make([]byte, l) + io.ReadFull(hkdf, okmComputed) + return bytes.Equal(okmComputed, okm) +} + +func validateVectors(filename string) bool { + vectors, err := os.Open(filename) + if err != nil { + panic(err) + } + defer vectors.Close() + + var segments []string + var l uint64 + var ikm, okm string + + scanner := bufio.NewScanner(vectors) + for scanner.Scan() { + segments = strings.Split(scanner.Text(), " = ") + + switch { + case strings.ToUpper(segments[0]) == "L": + l, err = strconv.ParseUint(segments[1], 10, 64) + if err != nil { + panic(err) + } + case strings.ToUpper(segments[0]) == "IKM": + ikm = segments[1] + case strings.ToUpper(segments[0]) == "OKM": + okm = segments[1] + } + } + return verifier(l, unhexlify(ikm), unhexlify(okm)) +} + +func main() { + if validateVectors("vectors/cryptography_vectors/KDF/hkdf-generated.txt") { + fmt.Println("HKDF OK.") + } else { + fmt.Println("HKDF failed.") + os.Exit(1) + } +} diff --git a/docs/development/custom-vectors/idea.rst b/docs/development/custom-vectors/idea.rst index 336cdf01..758a108e 100644 --- a/docs/development/custom-vectors/idea.rst +++ b/docs/development/custom-vectors/idea.rst @@ -29,4 +29,4 @@ project's Python bindings. Download link: :download:`verify_idea.py </development/custom-vectors/idea/verify_idea.py>` -.. _`Botan`: http://botan.randombit.net +.. _`Botan`: https://botan.randombit.net diff --git a/docs/development/custom-vectors/idea/generate_idea.py b/docs/development/custom-vectors/idea/generate_idea.py index c9f94024..2eb6996e 100644 --- a/docs/development/custom-vectors/idea/generate_idea.py +++ b/docs/development/custom-vectors/idea/generate_idea.py @@ -52,6 +52,7 @@ 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), "idea-cbc.txt") OFB_PATH = "tests/hazmat/primitives/vectors/ciphers/AES/OFB/OFBMMT128.rsp" diff --git a/docs/development/custom-vectors/rsa-oaep-sha2.rst b/docs/development/custom-vectors/rsa-oaep-sha2.rst new file mode 100644 index 00000000..36f256d7 --- /dev/null +++ b/docs/development/custom-vectors/rsa-oaep-sha2.rst @@ -0,0 +1,56 @@ +RSA OAEP SHA2 vector creation +============================= + +This page documents the code that was used to generate the RSA OAEP SHA2 +test vectors as well as code used to verify them against another +implementation. + + +Creation +-------- + +``cryptography`` was modified to allow the use of SHA2 in OAEP encryption. Then +the following python script was run to generate the vector files. + +.. literalinclude:: /development/custom-vectors/rsa-oaep-sha2/generate_rsa_oaep_sha2.py + +Download link: :download:`generate_rsa_oaep_sha2.py +</development/custom-vectors/rsa-oaep-sha2/generate_rsa_oaep_sha2.py>` + + +Verification +------------ + +A Java 8 program was written using `Bouncy Castle`_ to load and verify the test +vectors. + + +.. literalinclude:: /development/custom-vectors/rsa-oaep-sha2/VerifyRSAOAEPSHA2.java + +Download link: :download:`VerifyRSAOAEPSHA2.java +</development/custom-vectors/rsa-oaep-sha2/VerifyRSAOAEPSHA2.java>` + +Using the Verifier +------------------ + +Download and install the `Java 8 SDK`_. Initial verification was performed +using ``jdk-8u77-macosx-x64.dmg``. + +Download the latest `Bouncy Castle`_ JAR. Initial verification was performed +using ``bcprov-jdk15on-154.jar``. + +Set the ``-classpath`` to include the Bouncy Castle jar and the path to +``VerifyRSAOAEPSHA2.java`` and compile the program. + +.. code-block:: console + + $ javac -classpath ~/Downloads/bcprov-jdk15on-154.jar:./ VerifyRSAOAEPSHA2.java + +Finally, run the program with the path to the SHA-2 vectors: + +.. code-block:: console + + $ java -classpath ~/Downloads/bcprov-jdk15on-154.jar:./ VerifyRSAOAEPSHA2 + +.. _`Bouncy Castle`: https://www.bouncycastle.org/ +.. _`Java 8 SDK`: https://www.oracle.com/technetwork/java/javase/downloads/index.html diff --git a/docs/development/custom-vectors/rsa-oaep-sha2/VerifyRSAOAEPSHA2.java b/docs/development/custom-vectors/rsa-oaep-sha2/VerifyRSAOAEPSHA2.java new file mode 100644 index 00000000..e1bfd3d1 --- /dev/null +++ b/docs/development/custom-vectors/rsa-oaep-sha2/VerifyRSAOAEPSHA2.java @@ -0,0 +1,416 @@ +import java.io.BufferedReader; +import java.io.FileReader; +import java.io.IOException; +import java.math.BigInteger; +import java.security.AlgorithmParameters; +import java.security.GeneralSecurityException; +import java.security.KeyFactory; +import java.security.PrivateKey; +import java.security.Security; +import java.security.spec.AlgorithmParameterSpec; +import java.security.spec.MGF1ParameterSpec; +import java.security.spec.RSAPrivateKeySpec; +import java.util.Arrays; + +import javax.crypto.Cipher; +import javax.crypto.spec.OAEPParameterSpec; +import javax.crypto.spec.PSource; +import javax.xml.bind.DatatypeConverter; + +import org.bouncycastle.jce.provider.BouncyCastleProvider; + +class TestVectorData { + public BigInteger pub_key_modulus; + public BigInteger pub_key_exponent; + public BigInteger priv_key_public_exponent; + public BigInteger priv_key_modulus; + public BigInteger priv_key_exponent; + public BigInteger priv_key_prime_1; + public BigInteger priv_key_prime_2; + public BigInteger priv_key_prime_exponent_1; + public BigInteger priv_key_prime_exponent_2; + public BigInteger priv_key_coefficient; + public byte[] plaintext; + public byte[] ciphertext; +} + +class TestVectorLoader { + private static final String FILE_HEADER = "# RSA OAEP SHA2 vectors built"; + private static final String EXAMPLE_HEADER = "# ====="; + private static final String EXAMPLE = "# Example"; + private static final String PUBLIC_KEY = "# Public key"; + private static final String PUB_MODULUS = "# Modulus:"; + private static final String PUB_EXPONENT = "# Exponent:"; + private static final String PRIVATE_KEY = "# Private key"; + private static final String PRIV_MODULUS = "# Modulus:"; + private static final String PRIV_PUBLIC_EXPONENT = "# Public exponent:"; + private static final String PRIV_EXPONENT = "# Exponent:"; + private static final String PRIV_PRIME_1 = "# Prime 1:"; + private static final String PRIV_PRIME_2 = "# Prime 2:"; + private static final String PRIV_PRIME_EXPONENT_1 = "# Prime exponent 1:"; + private static final String PRIV_PRIME_EXPONENT_2 = "# Prime exponent 2:"; + private static final String PRIV_COEFFICIENT = "# Coefficient:"; + private static final String OAEP_EXAMPLE_HEADER = "# OAEP Example"; + private static final String MESSAGE = "# Message:"; + private static final String ENCRYPTION = "# Encryption:"; + + private BufferedReader m_reader = null; + private FileReader m_file_reader = null; + private TestVectorData m_data = null; + + TestVectorLoader() { + + } + + protected void finalize() { + close(); + } + + public void open(String path) throws IOException { + close(); + m_file_reader = new FileReader(path); + m_reader = new BufferedReader(m_file_reader); + m_data = new TestVectorData(); + } + + public void close() { + try { + if (m_reader != null) { + m_reader.close(); + m_reader = null; + } + if (m_file_reader != null) { + m_file_reader.close(); + m_file_reader = null; + } + m_data = null; + } catch (IOException e) { + System.out.println("Exception closing files"); + e.printStackTrace(); + } + } + + public TestVectorData loadNextTest() throws IOException { + if (m_file_reader == null || m_reader == null || m_data == null) { + throw new IOException("A test vector file must be opened first"); + } + + String line = m_reader.readLine(); + + if (line == null) { + // end of file + return null; + } + + if (line.startsWith(FILE_HEADER)) { + // start of file + skipFileHeader(m_reader); + line = m_reader.readLine(); + } + + if (line.startsWith(OAEP_EXAMPLE_HEADER)) { + // Next example, keep existing keys and load next message + loadMessage(m_reader, m_data); + return m_data; + } + + // otherwise it's a new example + if (!line.startsWith(EXAMPLE_HEADER)) { + throw new IOException("Test Header Missing"); + } + startNewTest(m_reader); + m_data = new TestVectorData(); + + line = m_reader.readLine(); + if (!line.startsWith(PUBLIC_KEY)) + throw new IOException("Public Key Missing"); + loadPublicKey(m_reader, m_data); + + line = m_reader.readLine(); + if (!line.startsWith(PRIVATE_KEY)) + throw new IOException("Private Key Missing"); + loadPrivateKey(m_reader, m_data); + + line = m_reader.readLine(); + if (!line.startsWith(OAEP_EXAMPLE_HEADER)) + throw new IOException("Message Missing"); + loadMessage(m_reader, m_data); + + return m_data; + } + + private byte[] unhexlify(String line) { + byte[] bytes = DatatypeConverter.parseHexBinary(line); + return bytes; + } + + private BigInteger readBigInteger(BufferedReader br) throws IOException { + return new BigInteger(br.readLine(), 16); + } + + private void skipFileHeader(BufferedReader br) throws IOException { + br.readLine(); // # # Derived from the NIST OAEP SHA1 vectors + br.readLine(); // # # Verified against the Bouncy Castle OAEP SHA2 implementation + br.readLine(); // # + } + + private void startNewTest(BufferedReader br) throws IOException { + String line = br.readLine(); + if (!line.startsWith(EXAMPLE)) + throw new IOException("Example Header Missing"); + } + + private void loadPublicKey(BufferedReader br, TestVectorData data) throws IOException { + String line = br.readLine(); + if (!line.startsWith(PUB_MODULUS)) + throw new IOException("Public Key Modulus Missing"); + data.pub_key_modulus = readBigInteger(br); + + line = br.readLine(); + if (!line.startsWith(PUB_EXPONENT)) + throw new IOException("Public Key Exponent Missing"); + data.pub_key_exponent = readBigInteger(br); + } + + private void loadPrivateKey(BufferedReader br, TestVectorData data) throws IOException { + String line = br.readLine(); + if (!line.startsWith(PRIV_MODULUS)) + throw new IOException("Private Key Modulus Missing"); + data.priv_key_modulus = readBigInteger(br); + + line = br.readLine(); + if (!line.startsWith(PRIV_PUBLIC_EXPONENT)) + throw new IOException("Private Key Public Exponent Missing"); + data.priv_key_public_exponent = readBigInteger(br); + + line = br.readLine(); + if (!line.startsWith(PRIV_EXPONENT)) + throw new IOException("Private Key Exponent Missing"); + data.priv_key_exponent = readBigInteger(br); + + line = br.readLine(); + if (!line.startsWith(PRIV_PRIME_1)) + throw new IOException("Private Key Prime 1 Missing"); + data.priv_key_prime_1 = readBigInteger(br); + + line = br.readLine(); + if (!line.startsWith(PRIV_PRIME_2)) + throw new IOException("Private Key Prime 2 Missing"); + data.priv_key_prime_2 = readBigInteger(br); + + line = br.readLine(); + if (!line.startsWith(PRIV_PRIME_EXPONENT_1)) + throw new IOException("Private Key Prime Exponent 1 Missing"); + data.priv_key_prime_exponent_1 = readBigInteger(br); + + line = br.readLine(); + if (!line.startsWith(PRIV_PRIME_EXPONENT_2)) + throw new IOException("Private Key Prime Exponent 2 Missing"); + data.priv_key_prime_exponent_2 = readBigInteger(br); + + line = br.readLine(); + if (!line.startsWith(PRIV_COEFFICIENT)) + throw new IOException("Private Key Coefficient Missing"); + data.priv_key_coefficient = readBigInteger(br); + } + + private void loadMessage(BufferedReader br, TestVectorData data) throws IOException { + String line = br.readLine(); + if (!line.startsWith(MESSAGE)) + throw new IOException("Plaintext Missing"); + data.plaintext = unhexlify(br.readLine()); + + line = br.readLine(); + if (!line.startsWith(ENCRYPTION)) + throw new IOException("Ciphertext Missing"); + data.ciphertext = unhexlify(br.readLine()); + } + +} + +public class VerifyRSAOAEPSHA2 { + + public enum SHAHash { + SHA1, SHA224, SHA256, SHA384, SHA512 + } + + private SHAHash m_mgf1_hash; + private SHAHash m_alg_hash; + private Cipher m_cipher; + private PrivateKey m_private_key; + private AlgorithmParameters m_algo_param; + + VerifyRSAOAEPSHA2(SHAHash mgf1_hash, SHAHash alg_hash, TestVectorData test_data) throws Exception { + + m_mgf1_hash = mgf1_hash; + m_alg_hash = alg_hash; + + MGF1ParameterSpec mgf1_spec = getMGF1ParameterSpec(m_mgf1_hash); + AlgorithmParameterSpec algo_param_spec = getAlgorithmParameterSpec(m_alg_hash, mgf1_spec); + + m_algo_param = AlgorithmParameters.getInstance("OAEP"); + m_algo_param.init(algo_param_spec); + + m_private_key = loadPrivateKey(test_data); + + m_cipher = getCipher(m_alg_hash); + } + + private Cipher getCipher(SHAHash alg_hash) throws GeneralSecurityException { + Cipher cipher = null; + + switch (alg_hash) { + + case SHA1: + cipher = Cipher.getInstance("RSA/ECB/OAEPwithSHA1andMGF1Padding", "BC"); + break; + + case SHA224: + cipher = Cipher.getInstance("RSA/ECB/OAEPwithSHA-224andMGF1Padding", "BC"); + break; + + case SHA256: + cipher = Cipher.getInstance("RSA/ECB/OAEPwithSHA-256andMGF1Padding", "BC"); + break; + + case SHA384: + cipher = Cipher.getInstance("RSA/ECB/OAEPwithSHA-384andMGF1Padding", "BC"); + break; + + case SHA512: + cipher = Cipher.getInstance("RSA/ECB/OAEPwithSHA-512andMGF1Padding", "BC"); + break; + } + + return cipher; + } + + private MGF1ParameterSpec getMGF1ParameterSpec(SHAHash mgf1_hash) { + MGF1ParameterSpec mgf1 = null; + + switch (mgf1_hash) { + + case SHA1: + mgf1 = MGF1ParameterSpec.SHA1; + break; + case SHA224: + mgf1 = MGF1ParameterSpec.SHA224; + break; + + case SHA256: + mgf1 = MGF1ParameterSpec.SHA256; + break; + + case SHA384: + mgf1 = MGF1ParameterSpec.SHA384; + break; + + case SHA512: + mgf1 = MGF1ParameterSpec.SHA512; + break; + } + + return mgf1; + } + + private AlgorithmParameterSpec getAlgorithmParameterSpec(SHAHash alg_hash, MGF1ParameterSpec mgf1_spec) { + + OAEPParameterSpec oaep_spec = null; + + switch (alg_hash) { + + case SHA1: + oaep_spec = new OAEPParameterSpec("SHA1", "MGF1", mgf1_spec, PSource.PSpecified.DEFAULT); + break; + + case SHA224: + oaep_spec = new OAEPParameterSpec("SHA-224", "MGF1", mgf1_spec, PSource.PSpecified.DEFAULT); + break; + + case SHA256: + oaep_spec = new OAEPParameterSpec("SHA-256", "MGF1", mgf1_spec, PSource.PSpecified.DEFAULT); + break; + + case SHA384: + oaep_spec = new OAEPParameterSpec("SHA-384", "MGF1", mgf1_spec, PSource.PSpecified.DEFAULT); + break; + + case SHA512: + oaep_spec = new OAEPParameterSpec("SHA-512", "MGF1", mgf1_spec, PSource.PSpecified.DEFAULT); + break; + } + + return oaep_spec; + } + + private PrivateKey loadPrivateKey(TestVectorData test_data) throws Exception { + KeyFactory kf = KeyFactory.getInstance("RSA"); + + RSAPrivateKeySpec keySpec = new RSAPrivateKeySpec(test_data.priv_key_modulus, test_data.priv_key_exponent); + + return kf.generatePrivate(keySpec); + } + + public void testDecrypt(byte[] plaintext, byte[] ciphertext) throws Exception { + System.out.println("Verifying OAEP with mgf1_hash: " + m_mgf1_hash + " alg_hash: " + m_alg_hash + " - " + + ciphertext.length + " bytes ciphertext - " + + plaintext.length + " bytes plaintext"); + + m_cipher.init(Cipher.DECRYPT_MODE, m_private_key, m_algo_param); + byte[] java_plaintext = m_cipher.doFinal(ciphertext); + + if (Arrays.equals(java_plaintext, plaintext) == false) { + throw new Exception("Verification failure - plaintext does not match after decryption."); + } + } + + public static void main(String[] args) { + Security.addProvider(new BouncyCastleProvider()); + + // assume current directory if no path given on command line + String vector_path = "./vectors/cryptography_vectors/asymmetric/RSA/oaep-custom"; + + if (args.length > 0) { + vector_path = args[0]; + } + + System.out.println("Vector file path: " + vector_path); + + try { + // loop over each combination of hash loading the vector file + // to verify for each + for (SHAHash mgf1_hash : SHAHash.values()) { + for (SHAHash alg_hash : SHAHash.values()) { + if (mgf1_hash.name().toLowerCase().equals("sha1") && + alg_hash.name().toLowerCase().equals("sha1")) { + continue; + } + String filename = "oaep-" + mgf1_hash.name().toLowerCase() + + "-" + alg_hash.name().toLowerCase() + ".txt"; + + System.out.println("Loading " + filename + "..."); + + TestVectorLoader loader = new TestVectorLoader(); + loader.open(vector_path + filename); + + TestVectorData test_data; + + // load each test in the file and verify + while ((test_data = loader.loadNextTest()) != null) { + VerifyRSAOAEPSHA2 verify = new VerifyRSAOAEPSHA2(mgf1_hash, alg_hash, test_data); + verify.testDecrypt(test_data.plaintext, test_data.ciphertext); + } + + System.out.println("Verifying " + filename + " completed successfully."); + } + } + + System.out.println("All verification completed successfully"); + + } catch (Exception e) { + // if any exception is thrown the verification has failed + e.printStackTrace(); + System.out.println("Verification Failed!"); + } + } +} diff --git a/docs/development/custom-vectors/rsa-oaep-sha2/generate_rsa_oaep_sha2.py b/docs/development/custom-vectors/rsa-oaep-sha2/generate_rsa_oaep_sha2.py new file mode 100644 index 00000000..bd5148f5 --- /dev/null +++ b/docs/development/custom-vectors/rsa-oaep-sha2/generate_rsa_oaep_sha2.py @@ -0,0 +1,128 @@ +# 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. + +from __future__ import absolute_import, division, print_function + +import binascii +import itertools +import os + +from cryptography.hazmat.backends.openssl.backend import backend +from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.primitives.asymmetric import padding, rsa + +from tests.utils import load_pkcs1_vectors, load_vectors_from_file + + +def build_vectors(mgf1alg, hashalg, filename): + vectors = load_vectors_from_file(filename, load_pkcs1_vectors) + + output = [] + for vector in vectors: + # RSA keys for this must be long enough to accommodate the length of + # the underlying hash function. This means we can't use the keys from + # the sha1 test vectors for sha512 tests because 1024-bit keys are too + # small. Instead we parse the vectors for the test cases, then + # generate our own 2048-bit keys for each. + private, _ = vector + skey = rsa.generate_private_key(65537, 2048, backend) + pn = skey.private_numbers() + examples = private["examples"] + output.append(b"# =============================================") + output.append(b"# Example") + output.append(b"# Public key") + output.append(b"# Modulus:") + output.append(format(pn.public_numbers.n, "x")) + output.append(b"# Exponent:") + output.append(format(pn.public_numbers.e, "x")) + output.append(b"# Private key") + output.append(b"# Modulus:") + output.append(format(pn.public_numbers.n, "x")) + output.append(b"# Public exponent:") + output.append(format(pn.public_numbers.e, "x")) + output.append(b"# Exponent:") + output.append(format(pn.d, "x")) + output.append(b"# Prime 1:") + output.append(format(pn.p, "x")) + output.append(b"# Prime 2:") + output.append(format(pn.q, "x")) + output.append(b"# Prime exponent 1:") + output.append(format(pn.dmp1, "x")) + output.append(b"# Prime exponent 2:") + output.append(format(pn.dmq1, "x")) + output.append(b"# Coefficient:") + output.append(format(pn.iqmp, "x")) + pkey = skey.public_key() + vectorkey = rsa.RSAPrivateNumbers( + p=private["p"], + q=private["q"], + d=private["private_exponent"], + dmp1=private["dmp1"], + dmq1=private["dmq1"], + iqmp=private["iqmp"], + public_numbers=rsa.RSAPublicNumbers( + e=private["public_exponent"], + n=private["modulus"] + ) + ).private_key(backend) + count = 1 + + for example in examples: + message = vectorkey.decrypt( + binascii.unhexlify(example["encryption"]), + padding.OAEP( + mgf=padding.MGF1(algorithm=hashes.SHA1()), + algorithm=hashes.SHA1(), + label=None + ) + ) + assert message == binascii.unhexlify(example["message"]) + ct = pkey.encrypt( + message, + padding.OAEP( + mgf=padding.MGF1(algorithm=mgf1alg), + algorithm=hashalg, + label=None + ) + ) + output.append( + b"# OAEP Example {0} alg={1} mgf1={2}".format( + count, hashalg.name, mgf1alg.name + ) + ) + count += 1 + output.append(b"# Message:") + output.append(example["message"]) + output.append(b"# Encryption:") + output.append(binascii.hexlify(ct)) + + return b"\n".join(output) + + +def write_file(data, filename): + with open(filename, "w") as f: + f.write(data) + + +oaep_path = os.path.join( + "asymmetric", "RSA", "pkcs-1v2-1d2-vec", "oaep-vect.txt" +) +hashalgs = [ + hashes.SHA1(), + hashes.SHA224(), + hashes.SHA256(), + hashes.SHA384(), + hashes.SHA512(), +] +for hashtuple in itertools.product(hashalgs, hashalgs): + if ( + isinstance(hashtuple[0], hashes.SHA1) and + isinstance(hashtuple[1], hashes.SHA1) + ): + continue + + write_file( + build_vectors(hashtuple[0], hashtuple[1], oaep_path), + "oaep-{0}-{1}.txt".format(hashtuple[0].name, hashtuple[1].name) + ) diff --git a/docs/development/custom-vectors/secp256k1.rst b/docs/development/custom-vectors/secp256k1.rst index b19bf4e4..e0579b4f 100644 --- a/docs/development/custom-vectors/secp256k1.rst +++ b/docs/development/custom-vectors/secp256k1.rst @@ -29,4 +29,4 @@ the following python script was run to generate the vector files. Download link: :download:`verify_secp256k1.py </development/custom-vectors/secp256k1/verify_secp256k1.py>` -.. _`pure Python ecdsa`: https://pypi.python.org/pypi/ecdsa +.. _`pure Python ecdsa`: https://pypi.org/project/ecdsa/ diff --git a/docs/development/custom-vectors/secp256k1/generate_secp256k1.py b/docs/development/custom-vectors/secp256k1/generate_secp256k1.py index 502a3ff6..d6a2071a 100644 --- a/docs/development/custom-vectors/secp256k1/generate_secp256k1.py +++ b/docs/development/custom-vectors/secp256k1/generate_secp256k1.py @@ -74,6 +74,7 @@ def write_file(lines, dest): print(line) print(line, file=dest) + source_path = os.path.join("asymmetric", "ECDSA", "FIPS_186-3", "SigGen.txt") dest_path = os.path.join("asymmetric", "ECDSA", "SECP256K1", "SigGen.txt") diff --git a/docs/development/custom-vectors/secp256k1/verify_secp256k1.py b/docs/development/custom-vectors/secp256k1/verify_secp256k1.py index 3d2c25b9..b236d77f 100644 --- a/docs/development/custom-vectors/secp256k1/verify_secp256k1.py +++ b/docs/development/custom-vectors/secp256k1/verify_secp256k1.py @@ -6,7 +6,7 @@ from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives.asymmetric import ec from cryptography.hazmat.primitives.asymmetric.utils import ( - encode_rfc6979_signature + encode_dss_signature ) from tests.utils import ( @@ -27,7 +27,7 @@ def verify_one_vector(vector): message = vector['message'] x = vector['x'] y = vector['y'] - signature = encode_rfc6979_signature(vector['r'], vector['s']) + signature = encode_dss_signature(vector['r'], vector['s']) numbers = ec.EllipticCurvePublicNumbers( x, y, diff --git a/docs/development/custom-vectors/seed.rst b/docs/development/custom-vectors/seed.rst index 290fb77a..8c4a7aab 100644 --- a/docs/development/custom-vectors/seed.rst +++ b/docs/development/custom-vectors/seed.rst @@ -29,4 +29,4 @@ project's Python bindings. Download link: :download:`verify_seed.py </development/custom-vectors/seed/verify_seed.py>` -.. _`Botan`: http://botan.randombit.net +.. _`Botan`: https://botan.randombit.net diff --git a/docs/development/custom-vectors/seed/generate_seed.py b/docs/development/custom-vectors/seed/generate_seed.py index d59597fd..5c62d671 100644 --- a/docs/development/custom-vectors/seed/generate_seed.py +++ b/docs/development/custom-vectors/seed/generate_seed.py @@ -51,6 +51,7 @@ def write_file(data, filename): with open(filename, "w") as f: f.write(data) + OFB_PATH = "vectors/cryptography_vectors/ciphers/AES/OFB/OFBMMT128.rsp" write_file(build_vectors(modes.OFB, OFB_PATH), "seed-ofb.txt") CFB_PATH = "vectors/cryptography_vectors/ciphers/AES/CFB/CFB128MMT128.rsp" |
