如何在PHP中实现相同的签名生成?

我有以下示例代码,用于使用自签名证书生成签名:

public static String generateSignature(String data) throws Exception {

        System.out.println("@@inside generateSignature: " + data);

        String signature;

        String jksFilepath = "E:\\test.jks";

        try {
            // Adding Security Provider for PKCS 12
            Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
            // Setting password for the e-Token

            // logging into token
            ks = KeyStore.getInstance("jks");


            FileInputStream fileInputStream = new FileInputStream(jksFilepath);

            // Loading Keystore
            // System.out.println("loading keystore");
            ks.load(fileInputStream, JKSPassword);
            Enumeration<String> e = ks.aliases();

            while (e.hasMoreElements()) {
                alias = e.nextElement();
                // System.out.println("Alias of the e-Token : "+ alias);

                UserCert = (X509Certificate) ks.getCertificate(alias);

                UserCertPubKey = (PublicKey) ks.getCertificate(alias).getPublicKey();

                // System.out.println("loading Private key");
                UserCertPrivKey = (PrivateKey) ks.getKey(alias, JKSPassword);
            }

            // Method Call to generate Signature
            signature = MakeSignature(data);

            return signature;

        } catch (Exception e) {
            e.printStackTrace();
            System.out.println("generateSignature" + e.getCause());
            throw new Exception();
        }

    }

    private static String MakeSignature(String data) {

        System.out.println("@@inside MakeSignature...");

        try {
            PrivateKey privateKey = (PrivateKey) ks.getKey(alias, JKSPassword);
            myPubCert = (X509Certificate) ks.getCertificate(alias);
            Store certs = new JcaCertStore(Arrays.asList(myPubCert));

            CMSSignedDataGenerator generator = new CMSSignedDataGenerator();

            generator.addSignerInfoGenerator(new JcaSimpleSignerInfoGeneratorBuilder().setProvider("BC").build("SHA256withRSA", privateKey, myPubCert));

            generator.addCertificates(certs);

            CMSTypedData data1 = new CMSProcessableByteArray(data.getBytes());

            CMSSignedData signed = generator.generate(data1, true);

            BASE64Encoder encoder = new BASE64Encoder();

            String signedContent = encoder.encode((byte[]) signed.getSignedContent().getContent());

            String envelopedData = encoder.encode(signed.getEncoded());

            return envelopedData;
        } catch (Exception e) {
            e.printStackTrace();
            System.out.println("MakeSignature ==" + e.getCause());
            return "";
        }
    }

也有一些相关的功能,但为了简短起见我没有添加。现在,我想使用PHP进行完全相同的操作,但是JKS不能在PHP上作为Java的密钥库使用。我尝试使用具有不同加密方法集的open_ssl函数,但是我没有得到与通过此Java代码获得的预期结果相同的预期结果(“不相同”是关于所生成签名的比特率和长度)。

有人可以帮我在PHP中实现相同的签名生成吗?

I think the PHP official document is very clear: http://php.net/manual/en/function.openssl-csr-new.php

Example #1 Creating a self-signed certificate

<?php
$dn = array(
    "countryName" => "GB",
    "stateOrProvinceName" => "Somerset",
    "localityName" => "Glastonbury",
    "organizationName" => "The Brain Room Limited",
    "organizationalUnitName" => "PHP Documentation Team",
    "commonName" => "Wez Furlong",
    "emailAddress" => "wez@example.com"
);

// Generate a new private (and public) key pair
$privkey = openssl_pkey_new(array(
    "private_key_bits" => 2048,
    "private_key_type" => OPENSSL_KEYTYPE_RSA,
));

// Generate a certificate signing request
$csr = openssl_csr_new($dn, $privkey, array('digest_alg' => 'sha256'));

// Generate a self-signed cert, valid for 365 days
$x509 = openssl_csr_sign($csr, null, $privkey, $days=365, array('digest_alg' => 'sha256'));

// Save your private key, CSR and self-signed cert for later use
openssl_csr_export($csr, $csrout) and var_dump($csrout);
openssl_x509_export($x509, $certout) and var_dump($certout);
openssl_pkey_export($privkey, $pkeyout, "mypassword") and var_dump($pkeyout);

// Show any errors that occurred here
while (($e = openssl_error_string()) !== false) {
    echo $e . "
";
}

Then you can call openssl_sign: http://php.net/manual/en/function.openssl-sign.php , use the generated private key to sign.

If you want to use the Java(JKS)'s key in PHP code, you should export the keys first, and then use PHP function load the keys.

The following code Java and PHP takes a private key from a PKCS12 keystore (keystore.pfx) and signs the content of the data.txt file. Using the same keystore and data both implementations return exactly the same output:

I used plain Java only (no bouncycastle) as the java.security classes can handle PKCS12 input very well:

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;
    }
}

And the PHP version:

<?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;
?>

I used OpenSSL to generate the PKCS12 keystore.pfx file:

# 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

You can sign the data.txt using OpenSSL as well:

openssl dgst -sha256 -sign private.pem < data.txt | openssl base64

All version will output the same result.

If you have a JKS keystore and want to use the private key stored in this keystore, you can export the JKS keystore to PKCS12:

keytool -importkeystore -srckeystore keystore.jks -destkeystore keystore.pfx \
    -srcstoretype JKS -deststoretype PKCS12 -deststorepass password \
    -srcalias alias -destalias 1

One more thing to note as this always seems to be confused:

You don't sign data using a certificate. You sign data using a (private) key. A certificate is more or less simply a piece of data signed with a private key. A self-signed certificate is signed with your own private key. While a certificate issued by a certificate authority (CA) is signed with the CA's private key.

In the above example the generated certificate signing request (CSR) and certificate and basically only created to import the private key into the PKCS12 keystore. You could use the plain private.pem key file as well for the signing purpose, but as you were using a PKCS12 keystore I did the same.