Подпись RSA, имеющая открытый ключ как текст, не проверенный в Java

Эта проблема генерируется в продолжение прошлого вопроса Как RSA проверить подпись в Java, которая была сгенерирована в php . Этот код работает для простого текста. Но теперь у меня есть требование подписать и проверить текст, который также имеет открытый ключ (кроме ключа проверки) в формате.

text1:text2:exported-public-key

Пример :

53965C38-E950-231A-8417-074BD95744A4:22-434-565-54544:MIIBCgKCAQEAxWg6ErfkN3xu8rk9WsdzjL5GpjAucMmOAQNeZcgMBxN+VmU43EnvsDLSxUZD1e/cvfP2t2/dzhtV6N2IvT7hveuo/zm3+bUK6AnAfo6pM1Ho0z4WetoYOrHdOVNMMPaytXiVkNlXyeWRF6rl9JOe94mMYWRJzygntiD44+MXsB6agsvQmB1l8thg/8+QHNOBBU1yC4pLQwwO2cb1+oIl0svESkGpzHk8xJUl5jL6dDnhqp8+01KE7AGHwvufrsw9TfVSAPH73lwo3mBMVXE4sfXBzC0/YwZ/8pz13ToYiN88DoqzcfD3+dtrjmpoMpymAA5FBc5c6xhPRcrn24KaiwIDAQAB

PHP-код:

$rsa = new Crypt_RSA();
$keysize=2048;
$pubformat = "CRYPT_RSA_PUBLIC_FORMAT_PKCS1";
$privformat = "CRYPT_RSA_PRIVATE_FORMAT_PKCS8";
$rsa->setPrivateKeyFormat(CRYPT_RSA_PRIVATE_FORMAT_PKCS8);
$rsa->setPublicKeyFormat(CRYPT_RSA_PUBLIC_FORMAT_PKCS1);
$d = $rsa->createKey($keysize);
$Kp = $d['publickey'];
$Ks = $d['privatekey'];

$rsa = new Crypt_RSA();
$rsa->setPrivateKeyFormat(CRYPT_RSA_PRIVATE_FORMAT_PKCS8);
$rsa->setPublicKeyFormat(CRYPT_RSA_PUBLIC_FORMAT_PKCS1);
$d = $rsa->createKey($keysize);
$Kver = $d['publickey'];
$KSign = $d['privatekey'];

$plainText = "53965C38-E950-231A-8417-074BD95744A4:22-434-565-54544:".$Kp;

// Signing
$hash = new Crypt_Hash('sha256');
$rsa = new Crypt_RSA();
$rsa->loadKey($KSign);
$rsa->setSignatureMode(CRYPT_RSA_ENCRYPTION_PKCS1);
$rsa->setHash('sha256');

$signature = $rsa->sign($plainText);
$signedHS = base64_encode($signature);

// Verification

$signature = base64_decode($signedHS);
$rsa->loadKey($Kver);
$status = $rsa->verify($plainText, $signature);

var_dump($status);

Код JAVA

import static java.nio.charset.StandardCharsets.UTF_8;
import java.io.ByteArrayOutputStream;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.io.ObjectInputStream;
import java.math.BigInteger;
import java.security.spec.X509EncodedKeySpec;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.Security;
import java.security.Signature;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.RSAPublicKeySpec;
//import java.util.Base64;
//import java.util.Base64.Decoder;
import org.apache.commons.codec.binary.Base64;

import javax.crypto.Cipher;

import org.bouncycastle.jce.provider.BouncyCastleProvider;

public class VerifySig {

public static RSAPublicKey fromPKCS1Encoding(byte[] pkcs1EncodedPublicKey) {
// --- parse public key ---
org.bouncycastle.asn1.pkcs.RSAPublicKey pkcs1PublicKey;
try {
pkcs1PublicKey = org.bouncycastle.asn1.pkcs.RSAPublicKey
.getInstance(pkcs1EncodedPublicKey);
} catch (Exception e) {
throw new IllegalArgumentException(
"Could not parse BER PKCS#1 public key structure", e);
}

// --- convert to JCE RSAPublicKey
RSAPublicKeySpec spec = new RSAPublicKeySpec(
pkcs1PublicKey.getModulus(), pkcs1PublicKey.getPublicExponent());
KeyFactory rsaKeyFact;
try {
rsaKeyFact = KeyFactory.getInstance("RSA");
} catch (NoSuchAlgorithmException e) {
throw new IllegalStateException("RSA KeyFactory should be available", e);
}
try {
return (RSAPublicKey) rsaKeyFact.generatePublic(spec);
} catch (InvalidKeySpecException e) {
throw new IllegalArgumentException(
"Invalid RSA public key, modulus and/or exponent invalid", e);
}
}public static void main(String[] args) throws Exception {
Security.addProvider(new BouncyCastleProvider());

String pkey = "MIIBCgKCAQEA+8fKYCT4QiFUdsJ7VdF4xCkVmq/Kwc/10Jl3ie6mvn8hEsC3NAtMJu+Od12gyWYsS0zBDiQ8h2pGZ7p4uWqenc01dRRrq+g968zmoCKPUllPUuR6v9o+wYTX/os4hgaQSBg7DQn4g3BEekcvyk6e6zAMvuhHjeqnrinhCMFgJUhFL8zFNoyaH559C0TNbR6BTKzOoikah8cKhu4UOga0tWDC0I2Ifus/sHOwVaOBkDFIzD6jBxDH/QF8FsrLLTocuIb7Y6lVxFPPtgiUJku6b7wKExV0bPJvm6/Xhv1GX1FpMrA0Ylzj5IFviuviwgo534EcZQ/Hx3aIf4oPG8jVTQIDAQAB";
byte[] dpkey = Base64.decodeBase64(pkey);
RSAPublicKey publicKey = fromPKCS1Encoding(dpkey);
String plainData = "53965C38-E950-231A-8417-074BD95744A4:22-434-565-54544:MIIBCgKCAQEArszIunGg3ievJOpgesYQsp3nPGgrW+3VwkivkkktOXUBRzb3G3mZzidEjG6LxNe/rrNe0UczmnSHQoSBxJCHyUnCWNfScBD66CFG4hLo5Z1gxrP8D2M2lCa6ap2PWcsKiWqlu38EinMeBjBvB4aYpF7+FkFy64ObxR4pfVZxnxradkD0HvvMPLMbyeHxeGqYf8orERf9jfuKTdY8V44rxht2D2fg2WhB1+XL0JulsPvgOaSK3RPnwi+RQAJbihCIh5Zznn0KQCs5pIWoT3XKe1DMpQuEmphSOY9ZUg3AwlOrpRV+565x6GCSc615/6nowmqKzE4T7qT5nbH+ctiEHQIDAQAB";
String data = "iD96rNeR51BF2TUZSaw+QhW8SnsMXE5AdJiDVmJk6LL55jC26PBCnqXrFo2lsQt8aWRsZc0bHFGCcuIbhHA+Duo1/PwrxTqC5BZFL/frqsRSVa+vpvGEnj3xe4iImTEasMicQzzaAG9IWIgkRZ272lUZ8PqdtTuqAsRIwir6fEsfVs5uIErEWM18R4JxlFBc3LDIjFOFemEPSVIEBHwWht1c/CrdTtxPRIiugEb1jdofEBUNcWPZgfvApVx5+0aS9WTl31AY+RMlvp+13P/FQgAMnH9rvBdopRIVsZUNlMf8AOE2afhLPfOgx+41rzCB2wGCrRGELbml466WJ3wYNQ==";
byte[] ciphertext = Base64.decodeBase64(data);
System.out.println(new String(plainData.getBytes(), UTF_8));
verifyBC(publicKey, plainData, ciphertext);
System.out.flush();
}

private static void verifyBC(PublicKey publicKey, String plainData,
byte[] ciphertext) throws Exception {
// what should work (for PKCS#1 v1.5 signatures), requires Bouncy Castle provider
//Signature sig = Signature.getInstance( "SHA256withRSAandMGF1");
Signature sig = Signature.getInstance( "SHA256withRSA");
sig.initVerify(publicKey);
sig.update(plainData.getBytes(UTF_8));
System.out.println(sig.verify(ciphertext));
}
}

Это не дало никакой ошибки, но просто возвращает false при использовании открытого ключа в plainText. Если попытаться после удаления с открытым ключом, он работает и вернуть true.

PHP работает нормально, и подпись проверяется во всех случаях.

Я подозреваю, что Java не может проверить данные, имеющие текстовый или открытый ключ base 64 в виде текста?

ОБНОВЛЕНИЕ: я сравниваю двоичные байты обоих раз, и результат показывает незначительную разницу.

Первый случай

  1. PHP -> # C: sQ
  2. JAVA -> / # C: sQ

Второй случай

  1. PHP -> ] Q0l O +
  2. JAVA -> ] Q0l

Если php base64 не совместим с apache base 64?

5

Решение

Когда я запускаю код PHP, я замечаю, что $Kp переменная содержит ключ в неправильном формате:

-----BEGIN RSA PUBLIC KEY-----
MIIBCgKCAQEAqCJ/2E+YZvXJyabQmi0zZlaXXGbfXHt8KYS27i+PAJKBODmevTrS
w59S5AOy2l7lB4z5mYHuwdT6bm6YYXgE0gnoX/b2L65xdD9XtlenS4Zm15TVTdR5
zde4nBa0QPKfhFvthOmdPr9xDhDb8Rojy/phX+Ftva33ceTXoB+CtLyidMWbQmUh
ZufnI7MwIOPAIzXNJJ85eyUjBdoNMwlAPZo9vYQWeiwYGyP1fjQwEWZgjCH/LJjl
sNR1X9vp5oi8/4omdnFRvKLpkd5R7WMmMfAyAXe7tcfMSXuVAgMWEj9ZG0ELpXbG
S3CK6nvOp2gFF+AjHo9bCrh397jYotE3HQIDAQAB
-----END RSA PUBLIC KEY-----

Когда я убираю все лишнее форматирование и экспортирую ключ как одну строку base64, это работает.

Код PHP:

function extract_key($pkcs1) {
# strip out -----BEGIN/END RSA PUBLIC KEY-----, line endings, etc
$temp = preg_replace('#.*?^-+[^-]+-+#ms', '', $pkcs1, 1);
$temp = preg_replace('#-+[^-]+-+#', '', $temp);
return str_replace(array("\r", "\n", ' '), '', $temp);
}

$rsa = new Crypt_RSA();
$keysize=2048;
$pubformat = "CRYPT_RSA_PUBLIC_FORMAT_PKCS1";
$privformat = "CRYPT_RSA_PRIVATE_FORMAT_PKCS8";
$rsa->setPrivateKeyFormat(CRYPT_RSA_PRIVATE_FORMAT_PKCS8);
$rsa->setPublicKeyFormat(CRYPT_RSA_PUBLIC_FORMAT_PKCS1);
$d = $rsa->createKey($keysize);
$Kp = $d['publickey'];
$Ks = $d['privatekey'];$rsa = new Crypt_RSA();
$rsa->setPrivateKeyFormat(CRYPT_RSA_PRIVATE_FORMAT_PKCS8);
$rsa->setPublicKeyFormat(CRYPT_RSA_PUBLIC_FORMAT_PKCS1);
$d = $rsa->createKey($keysize);
$Kver = $d['publickey'];
$KSign = $d['privatekey'];

file_put_contents("pub_verify_key.txt",extract_key($Kver));

$plainText = "53965C38-E950-231A-8417-074BD95744A4:22-434-565-54544:".extract_key($Kp);

file_put_contents("plain.txt",$plainText);

// Signing
$hash = new Crypt_Hash('sha256');
$rsa = new Crypt_RSA();
$rsa->loadKey($KSign);
$rsa->setSignatureMode(CRYPT_RSA_ENCRYPTION_PKCS1);
$rsa->setHash('sha256');

$signature = $rsa->sign($plainText);

$signedHS = base64_encode($signature);

file_put_contents("signedkey.txt", $signedHS);

// Verification

$signature = base64_decode($signedHS);
$rsa->loadKey($Kver);
$status = $rsa->verify($plainText, $signature);

var_dump($status);

Java-код:

import static java.nio.charset.StandardCharsets.UTF_8;
import java.io.ByteArrayOutputStream;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.io.ObjectInputStream;
import java.io.File;
import java.io.FileReader;
import java.math.BigInteger;
import java.security.spec.X509EncodedKeySpec;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.Security;
import java.security.Signature;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.RSAPublicKeySpec;
import org.apache.commons.codec.binary.Base64;

import javax.crypto.Cipher;

import org.bouncycastle.jce.provider.BouncyCastleProvider;

public class VerifySig {

public static RSAPublicKey fromPKCS1Encoding(byte[] pkcs1EncodedPublicKey) {
// --- parse public key ---
org.bouncycastle.asn1.pkcs.RSAPublicKey pkcs1PublicKey;
try {
pkcs1PublicKey = org.bouncycastle.asn1.pkcs.RSAPublicKey
.getInstance(pkcs1EncodedPublicKey);
} catch (Exception e) {
throw new IllegalArgumentException(
"Could not parse BER PKCS#1 public key structure", e);
}

// --- convert to JCE RSAPublicKey
RSAPublicKeySpec spec = new RSAPublicKeySpec(
pkcs1PublicKey.getModulus(), pkcs1PublicKey.getPublicExponent());
KeyFactory rsaKeyFact;
try {
rsaKeyFact = KeyFactory.getInstance("RSA");
} catch (NoSuchAlgorithmException e) {
throw new IllegalStateException("RSA KeyFactory should be available", e);
}
try {
return (RSAPublicKey) rsaKeyFact.generatePublic(spec);
} catch (InvalidKeySpecException e) {
throw new IllegalArgumentException(
"Invalid RSA public key, modulus and/or exponent invalid", e);
}
}public static void main(String[] args) throws Exception {
Security.addProvider(new BouncyCastleProvider());

String pkey = fromFile("pub_verify_key.txt");
byte[] dpkey = Base64.decodeBase64(pkey);
RSAPublicKey publicKey = fromPKCS1Encoding(dpkey);
String plainData = fromFile("plain.txt");
String data = fromFile("signedkey.txt");
byte[] ciphertext = Base64.decodeBase64(data);
System.out.println(new String(plainData.getBytes(), UTF_8));
verifyBC(publicKey, plainData, ciphertext);
System.out.flush();
}

private static void verifyBC(PublicKey publicKey, String plainData,
byte[] ciphertext) throws Exception {
// what should work (for PKCS#1 v1.5 signatures), requires Bouncy Castle provider
//Signature sig = Signature.getInstance( "SHA256withRSAandMGF1");
Signature sig = Signature.getInstance( "SHA256withRSA");
sig.initVerify(publicKey);
sig.update(plainData.getBytes(UTF_8));
System.out.println(sig.verify(ciphertext));
}

private static String fromFile(String filename) {
StringBuilder builder = new StringBuilder(8000);
try {
FileReader reader = new FileReader(new File(filename));
int c;
while((c = reader.read()) != -1) {
builder.append((char)c);
}
} catch(IOException ioe) {
throw new RuntimeException(ioe);
}
return builder.toString();
}
}
2

Другие решения

Других решений пока нет …