417 lines
14 KiB
Java
417 lines
14 KiB
Java
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!");
|
|
}
|
|
}
|
|
}
|