| 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!"); |
| } |
| } |
| } |