0

I'm creating a license key generator that RSA signs base64 encoded license key data and appends signature to license data.On the end user side signature is verified using the public key that is bundled with application.We have applications running on .net, php, java, and other platforms.License model should be same for all. Now to the trouble:I have created 2048bit public/private key in C# using the built in cryptography provider and exported the keys in XML format to get the key BigInteger components transparent in b64 encoding.License key generator should work in php and I'm using the phpseclib to import the XML keys and sign the data.Php code:

function sign($data)
{
    $RSA_PRIVATE_KEY = "<RSAKeyValue>
<Modulus>oPmCaLewqKTkRPpKLHz+9hEbrUbVdKNkIxzm4h5/wmuId6PSx6ntV2T44/NSzfx56LffyXvzx27GYEKk4zffpPYKdRIGReZVqfl2U5K3jcuv0h4657ge0nZ5W9vrYMDSFjgtZOSw8t2opWC8IZVUI3X9W8mp1Z3xqtwREpLC/kV0YE1Kmem8k8B4Tv1+qj5C6oPJjhy+T3JipYOQIyVRUoCdo3EHpDQ6K7twKD+Si/rASHyvoCJZTPvWI1w9y9FD8CxstkY7+ap8J9DMczyiFsE4kJtziBKZFph4ZRqz4kSA0TXj0lXhGEAcn1IDjhV5W37PdsWOkUTxBH7Bm7mpuw==</Modulus>
<Exponent>AQAB</Exponent>
<P>xU8K0Z1Mn0cLOHrwMIujXvtJbXpmjFfyOkm+WkeNV2hjDmdgMibJJvhqPEYPSabEHF4us8H4IeEqeInGK04LrYmbe3IVgmuGdpacR01WISbqXiarsKbcHkR65mfotQmxay/n3Swi8hN9yTanSJyhoHFBiJ0qYM0VNdamu7LDeGk=</P>
<Q>0Nukxy2o/o7z6NeR9g+dUcOQzXfWS2Bu4vAXyHGpJArGcIzl4Z2jNuUztGj5sO5b+9SQs4tgijsjbsEQUNN7R1+PB7Zwc50PvGgEvGaIJkPphGoJMNCw4Q2I8tNPb86cEd4WLJ8E6ad93vX0tyZAnn3LrjPg3Bvvxtq157jZLIM=</Q>
<DP>tGRV0dtsyFrdyV+s5dVlIlvAgFVeGIX3so7leAjfEsEff3XIH1ISqoyIJF8xbvcHaaA6NqLqx57jg50DD2iliJ29B5oATGMeZqHAc/gi/OBlensEkdecfBfD/Y+W1J3uFb+Qz0ehE436fNJ5EwwRQW0Kq2p16lbWQ4jim80OpbE=</DP>
<DQ>vML2bumuhbrvaK6EBa5hEce9dGXtcJyMO2ChLhDDvIZciNZe4YUWQQPvsgr6OFWFHtojmZHLQ8NlJ7EnrNUl4wDThTX29haqZS5hsWC9hk/0mi83dT33zr7r2gLvFW7XETL2OYfS6dXt5ffHH0xcNKIe1qeef3BkSgXbR72B0j8=</DQ>
<InverseQ>TffUf2HXQOmVEUo6rinASIOJOfwfucd+MjwDaK61bISFY5J2n3MYRX4wEiFxXlcYgXnR9CsbwRVhUDIDzLuB+ykAG62LkbqCKUYjzyN3RDbc/MSj/sXSw60pTc4DesVhQlumd0zGUOxsFkjOlUowxwIDENiSVFew1IfAOZiakpk=</InverseQ>
<D>AKHPgpxrXn4nQfi+9HsZKoYurE4sOw+ug6TIE03jWolpjm606SvK+XOKtqUXR3pyUBjzZqsh7eqKxN3+H8DxvogTdRo5BBU/c4dokN4b8maWWNDdkliwEPYoy9Tf5mVbbdLn+rlwfcOjtzfbWpZnhNbLGTfVfuKRNwaI2qB7kNwGB1fd1t4xMLWNozgoxFuiiKFbJKmLEj9zHP/KqjEnOH/zEuUBnXiqGMnCMmD7KnD5WqcqSDOahn0TBFlMxV+9Ul2447bu5LTGWhm3RBPEGgtjMboKC3PlgqwYDpC2gbzX5ZsBNCiuGgumxBHgOfAIOVzI01MZTFEpTcTt3BUrwQ==</D>
</RSAKeyValue>";

    $RSA = new Crypt_RSA();
    $RSA->loadKey($RSA_PRIVATE_KEY, CRYPT_RSA_PRIVATE_FORMAT_XML);
    $RSA->setHash(CRYPT_SHA512);
    $RSA->setSignatureMode(CRYPT_RSA_SIGNATURE_PKCS1);
    return $RSA->sign($data);
}

C# Code:

private String signData(String data) {
    byte[] private_key_modulus_array = Convert.FromBase64String(B64_MODULUS);
    byte[] private_key_exponent_array = Convert.FromBase64String(B64_EXPONENT);
    byte[] private_key_p_array = Convert.FromBase64String(B64_P);
    byte[] private_key_q_array = Convert.FromBase64String(B64_Q);
    byte[] private_key_dp_array = Convert.FromBase64String(B64_DP);
    byte[] private_key_dq_array = Convert.FromBase64String(B64_DQ);
    byte[] private_key_inverseQ_array = Convert.FromBase64String(B64_INVERSE_Q);
    byte[] private_key_d_array = Convert.FromBase64String(B64_D);

    //Create a new instance of RSACryptoServiceProvider.
    RSACryptoServiceProvider RSA = new RSACryptoServiceProvider();
    RSAParameters RSAKeyInfo = new RSAParameters();

    //Set RSAKeyInfo to the private key values. 
    RSAKeyInfo.Modulus = private_key_modulus_array;
    RSAKeyInfo.P = private_key_p_array;
    RSAKeyInfo.Q = private_key_q_array;
    RSAKeyInfo.DP = private_key_dp_array;
    RSAKeyInfo.DQ = private_key_dq_array;
    RSAKeyInfo.D = private_key_d_array;
    RSAKeyInfo.InverseQ = private_key_inverseQ_array;
    RSAKeyInfo.Exponent = private_key_exponent_array;
    RSA.ImportParameters(RSAKeyInfo);
  
    return Convert.ToBase64String(RSA.SignData(Encoding.ASCII.GetBytes(data), new SHA512CryptoServiceProvider()));
}

Generated signature is not same when generated with C# and PHP. I'm thinking that maybe the phpseclib did not parse the private key correctly??

On the C# side signature is same when created with built in cryptography and bouncycastle lib.

What should I do? Do I need any other key format to achieve consistency??

Thanx for help.

miken32
  • 42,008
  • 16
  • 111
  • 154
Brlja
  • 364
  • 3
  • 14
  • Crypt_RSA as in http://pear.php.net/package/Crypt_RSA ? And "same" means "can be validated with the appropriate signature methods"? – VolkerK Feb 07 '15 at 10:36
  • Its pure php, cryptography library is: http://phpseclib.sourceforge.net yes C# signature does not validate in php, tried that – Brlja Feb 07 '15 at 10:40
  • 1
    +1, It looks like you've done everything correctly in this PHP snippet as far as I can tell. I'm guessing that the C# code formats the data that gets signed differently. Could you post the relevant C# code that you want to be consistent? – President James K. Polk Feb 07 '15 at 14:31
  • Thank you for your answer greg, I will post C# code when I get home. Also I might try creating test hash on both sides to make sure the input data binary is the same – Brlja Feb 07 '15 at 14:55
  • Here's the code. I made a test SHA512 hash on C# and php and its same so problem is in keys... – Brlja Feb 07 '15 at 18:25

3 Answers3

3

Make sure you're using the same padding mode when signing as you do when verifying the signature, as James K Polk demonstrates.

However, if you want to be secure, use RSASSA-PSS with e=65537 and MFG1+SHA256 instead of PKCS1v1.5 padding (phpseclib supports both).

Also consider dropping RSA altogether in favor of Ed25519, which is available in PHP 7.2 or from PECL (PHP). On the other end, get libsodium.net (C#).

On the PHP end:

$message = 'Whatever you want';
$signature = sodium_crypto_sign_detached($message, $secretKey);
// ...
if (sodium_crypto_sign_verify_detached($signature, $message, $publicKey)) {
    // Valid signature for message and public key
}

On the C# end:

var message = "whatever you want";
// privateKey is a byte[] of length 64
// publicKey is a byte[] of length 32

// get a signature for the message
var signature = PublicKeyAuth.SignDetached(message, privateKey);

// check the signature for the message
if (PublicKeyAuth.VerifyDetached(signature, message, publicKey))
{
    // message ok
}
Scott Arciszewski
  • 33,610
  • 16
  • 89
  • 206
0

The difference is caused by different formatting of the data prior to applying the RSA exponentiation primitive. The RSACryptoServiceProvider SignData method uses a formatting sometimes referred to as a "raw" signature. The phpseclib rsa module instead uses the formatting as specified in PKCS#1 version 1.5.

You should be able to get compatible results by using instead on the .NET side the RSAPKCS1SignatureFormatter class. Here is a small snippet to illustrate:

    static byte[] PKCS1Signature(string content, RSACryptoServiceProvider rsaKey) {
        byte[] contentBytes = Encoding.UTF8.GetBytes (content);
        RSAPKCS1SignatureFormatter signer = new RSAPKCS1SignatureFormatter (rsaKey);
        signer.SetHashAlgorithm ("SHA512");
        SHA512 hasher = SHA512.Create ();
        byte[] hash = hasher.ComputeHash (contentBytes);
        byte[] SignedHash = signer.CreateSignature (hash);
        return SignedHash;
    }

This answer on a related question regards RSA encryption, whereas this question is about RSA signatures.

Community
  • 1
  • 1
President James K. Polk
  • 40,516
  • 21
  • 95
  • 125
-2

I fixed the issue using the openssl on the php side and BouncyCastle on the C# side.
Key is pem format.

Here's how 512bit test key looks:

-----BEGIN RSA PRIVATE KEY-----
MIIBOgIBAAJBANDiE2+Xi/WnO+s120NiiJhNyIButVu6zxqlVzz0wy2j4kQVUC4Z
RZD80IY+4wIiX2YxKBZKGnd2TtPkcJ/ljkUCAwEAAQJAL151ZeMKHEU2c1qdRKS9
sTxCcc2pVwoAGVzRccNX16tfmCf8FjxuM3WmLdsPxYoHrwb1LFNxiNk1MXrxjH3R
6QIhAPB7edmcjH4bhMaJBztcbNE1VRCEi/bisAwiPPMq9/2nAiEA3lyc5+f6DEIJ
h1y6BWkdVULDSM+jpi1XiV/DevxuijMCIQCAEPGqHsF+4v7Jj+3HAgh9PU6otj2n
Y79nJtCYmvhoHwIgNDePaS4inApN7omp7WdXyhPZhBmulnGDYvEoGJN66d0CIHra
I2SvDkQ5CmrzkW5qPaE2oO7BSqAhRZxiYpZFb5CI
-----END RSA PRIVATE KEY-----
-----BEGIN PUBLIC KEY-----
MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBANDiE2+Xi/WnO+s120NiiJhNyIButVu6
zxqlVzz0wy2j4kQVUC4ZRZD80IY+4wIiX2YxKBZKGnd2TtPkcJ/ljkUCAwEAAQ==
-----END PUBLIC KEY-----

Keys are generated with OpenSSl utility: www.openssl.org
You can get a private/public key pair using:

openssl genrsa 512 >private_key.txt
openssl rsa -pubout <private_key.txt >public_key.txt

512 is the key bitsize.

PHP Data signature:

    function signData($message)
    {
        $private_key = <<<EOD
-----BEGIN RSA PRIVATE KEY-----
MIIBOgIBAAJBANDiE2+Xi/WnO+s120NiiJhNyIButVu6zxqlVzz0wy2j4kQVUC4Z
RZD80IY+4wIiX2YxKBZKGnd2TtPkcJ/ljkUCAwEAAQJAL151ZeMKHEU2c1qdRKS9
sTxCcc2pVwoAGVzRccNX16tfmCf8FjxuM3WmLdsPxYoHrwb1LFNxiNk1MXrxjH3R
6QIhAPB7edmcjH4bhMaJBztcbNE1VRCEi/bisAwiPPMq9/2nAiEA3lyc5+f6DEIJ
h1y6BWkdVULDSM+jpi1XiV/DevxuijMCIQCAEPGqHsF+4v7Jj+3HAgh9PU6otj2n
Y79nJtCYmvhoHwIgNDePaS4inApN7omp7WdXyhPZhBmulnGDYvEoGJN66d0CIHra
I2SvDkQ5CmrzkW5qPaE2oO7BSqAhRZxiYpZFb5CI
-----END RSA PRIVATE KEY-----
EOD;

        $binary_signature = "";
        openssl_sign($message, $binary_signature, $private_key, OPENSSL_ALGO_SHA1);
        return base64_encode($binary_signature);
    }

C# signature validation using only public key and BouncyCastle:

public bool verifySignature(byte[] signatureBytes, String message)
{
  AsymmetricKeyParameter publicKey = (AsymmetricKeyParameter)new PemReader(newStringReader(PUBLIC_KEY)).ReadObject();
  ISigner sig = SignerUtilities.GetSigner("SHA1withRSA");
  sig.Init(false, publicKey);

  byte[] messageBytes = Encoding.ASCII.GetBytes(message);
  sig.BlockUpdate(messageBytes, 0, messageBytes.Length);
  return sig.VerifySignature(signatureBytes);
}

Plain and simple. I cant believe I lost few days on this...
Anyways thank you for assistance.

Brlja
  • 364
  • 3
  • 14