Следующий код Java и PHP берет закрытый ключ из хранилища ключей PKCS12 (keystore.pfx
) и подписывает содержимое файла data.txt
. При использовании одного и того же хранилища ключей и данных обе реализации возвращают абсолютно одинаковые выходные данные:
Я использовал только простую Java (без упругости), поскольку классы java.security
могут очень хорошо обрабатывать ввод PKCS12:
public static void main(String[] args) throws Exception {
String keyStoreFile = "keystore.pfx";
char[] password = "password".toCharArray();
String dataFile = "data.txt";
PrivateKey priv = loadPrivateKey(keyStoreFile, password);
byte[] signature = signData(priv, dataFile);
System.out.println(Base64.getEncoder().encodeToString(signature));
}
private static byte[] signData(PrivateKey priv, String dataFile) throws Exception {
Signature dsa = Signature.getInstance("SHA256withRSA");
dsa.initSign(priv);
try (FileInputStream fis = new FileInputStream(dataFile);
BufferedInputStream bufin = new BufferedInputStream(fis);) {
byte[] buffer = new byte[1024];
int len;
while ((len = bufin.read(buffer)) >= 0) {
dsa.update(buffer, 0, len);
}
bufin.close();
byte[] realSig = dsa.sign();
return realSig;
}
}
private static PrivateKey loadPrivateKey(String keyStoreFile, char[] password) throws Exception {
try (FileInputStream fin = new FileInputStream(keyStoreFile)) {
KeyStore ks = KeyStore.getInstance("PKCS12", "SunJSSE");
ks.load(fin, password);
PrivateKey priv = (PrivateKey) ks.getKey("1", password);
return priv;
}
}
И версия PHP:
<?php
//data you want to sign
$data = file_get_contents("data.txt");
$cert_store = file_get_contents("keystore.pfx");
openssl_pkcs12_read($cert_store, $cert_info, "password");
//create signature
openssl_sign($data, $signature, $cert_info['pkey'], OPENSSL_ALGO_SHA256);
//finally encode
$r = base64_encode($signature);
print $r;
?>
Я использовал OpenSSL для генерации файла PKCS12 keystore.pfx
:
# generate new RSA private key
openssl genrsa -out private.pem 1024
# CSR and signed certificate are needed to export as PKCS12 store
openssl req -new -key private.pem -out certificate.csr
openssl x509 -req -days 365 -in certificate.csr -signkey private.pem -out certificate.crt
# export as PKCS12 keystore
openssl pkcs12 -export -out keystore.pfx -inkey private.pem -in certificate.crt -passout pass:password
Вы также можете подписать data.txt
, используя OpenSSL:
openssl dgst -sha256 -sign private.pem < data.txt | openssl base64
Все версии будут выводить один и тот же результат.
Если у вас есть хранилище ключей JKS и вы хотите использовать закрытый ключ, хранящийся в этом хранилище ключей, вы можете экспортировать хранилище ключей JKS в PKCS12:
keytool -importkeystore -srckeystore keystore.jks -destkeystore keystore.pfx \
-srcstoretype JKS -deststoretype PKCS12 -deststorepass password \
-srcalias alias -destalias 1
Еще одна вещь, на которую следует обратить внимание, так как это всегда кажется запутанным:
Вы не подписываете данные, используя сертификат. Вы подписываете данные, используя (закрытый) ключ. Сертификат - это более или менее простая часть данных, подписанная закрытым ключом. Самозаверяющий сертификат подписывается вашим личным ключом. В то время как сертификат, выданный центром сертификации (ЦС), подписан с помощью закрытого ключа ЦС.
В приведенном выше примере сгенерированный запрос на подпись сертификата (CSR) и сертификат, в основном, созданы только для импорта закрытого ключа в хранилище ключей PKCS12. Вы также можете использовать простой ключ private.pem
для подписи, но так как вы использовали хранилище ключей PKCS12, я сделал то же самое.