Несогласованная подпись RSA на разных платформах

Я создаю генератор лицензионных ключей, который RSA подписывает данные лицензионного ключа в кодировке base64 и добавляет подпись к данным лицензии. На стороне конечного пользователя подпись проверяется с использованием открытого ключа, который связан с приложением. У нас есть приложения, работающие на .net, php , java и другие платформы. Модель лицензии должна быть одинаковой для всех. Теперь к проблеме: я создал 2048-битный открытый/закрытый ключ на С#, используя встроенный провайдер криптографии, и экспортировал ключи в формате XML, чтобы получить ключевые компоненты BigInteger прозрачными в кодировке b64. Генератор лицензионных ключей должен работать в php, и я используя phpseclib для импорта ключей XML и подписи кода data.Php:

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

Код С#:

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

Сгенерированная подпись отличается от сгенерированной с помощью C# и PHP. Я думаю, что, возможно, phpseclib неправильно разобрал закрытый ключ??

На стороне C# подпись такая же, как и при создании со встроенной криптографией и bouncycastle lib.

Что я должен делать? Нужен ли мне какой-либо другой ключевой формат для достижения согласованности??

Спасибо за помощь.


person Brlja    schedule 07.02.2015    source источник
comment
Crypt_RSA как в pear.php.net/package/Crypt_RSA? И те же средства могут быть проверены соответствующими методами подписи?   -  person VolkerK    schedule 07.02.2015
comment
Его чистая библиотека php, криптографическая библиотека: phpseclib.sourceforge.net да подпись С# не проверяется в php, пробовал это   -  person Brlja    schedule 07.02.2015
comment
+1, Насколько я могу судить, вы все сделали правильно в этом фрагменте PHP. Я предполагаю, что код С# форматирует данные, которые подписываются по-разному. Не могли бы вы опубликовать соответствующий код C#, который вы хотите согласовать?   -  person President James K. Polk    schedule 07.02.2015
comment
Спасибо за ваш ответ, Грег, я опубликую код C #, когда вернусь домой. Также я могу попробовать создать тестовый хэш с обеих сторон, чтобы убедиться, что двоичный файл входных данных одинаков.   -  person Brlja    schedule 07.02.2015
comment
Вот код. Я сделал тестовый хэш SHA512 на С# и php, и он такой же, поэтому проблема в ключах...   -  person Brlja    schedule 07.02.2015


Ответы (3)


Убедитесь, что вы используете тот же режим заполнения при подписании, что и при проверке подписи, как демонстрирует Джеймс К. Полк.

Однако, если вы хотите быть в безопасности, используйте RSASSA- PSS с e=65537 и MFG1+SHA256 вместо заполнения PKCS1v1.5 (phpseclib поддерживает оба варианта).

Также рассмотрите возможность полного отказа от RSA в пользу Ed25519, который доступен в PHP 7.2 или из PECL (PHP). На другом конце получите libsodium.net (C#).

В конце PHP:

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

В конце С#:

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
}
person Scott Arciszewski    schedule 07.03.2016

Разница вызвана разным форматированием данных до применения примитива возведения в степень RSA. Метод RSACryptoServiceProvider SignData использует форматирование, которое иногда называют «сырой» подписью. Вместо этого модуль rsa phpseclib использует форматирование, указанное в PKCS#1 версии 1.5. .

Вы должны быть в состоянии получить совместимые результаты, используя вместо этого на стороне .NET файл класс RSAPKCS1SignatureFormatter. Вот небольшой фрагмент для иллюстрации:

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

Этот ответ на связанный вопрос касается шифрования RSA, тогда как этот вопрос касается RSA подписи.

person President James K. Polk    schedule 07.02.2015
comment
Не используйте PKCS#1 версии 1.5. Это серьезно небезопасно. - person Scott Arciszewski; 06.03.2016
comment
@ScottArciszewski: я им не пользуюсь. - person President James K. Polk; 07.03.2016
comment
Ваш ответ использует его. - person Scott Arciszewski; 07.03.2016
comment
В вашем ответе используется PCKS1v1.5, и я говорю не использовать PKCS1v1.5, потому что это небезопасно. Не знаю, как вы ожидали продолжения разговора. - person Scott Arciszewski; 07.03.2016
comment
Отклонено из-за предложения PKCS # 1v1.5, поскольку оно небезопасно. Вместо этого следует использовать OAEP. - person Aaron Toponce; 07.03.2016
comment
@ScottArciszewski: Я ничего подобного не делаю, как может видеть любой, кто читает и вопрос, и этот ответ. - person President James K. Polk; 07.03.2016
comment
@jameskpolk ты в порядке? Pkcs1 появляется в вашем ответе четыре раза, один из которых является ссылкой на документы класса msdn, в которых прямо говорится, что он создает подпись pkcs1 версии 1.5. Почему вы все время говорите, что его явно нет? - person Seth Battin; 15.11.2017
comment
@SethBattin: Верно. Это защищенная подпись PKCS 1.5. Я хорошо, а ты как? - person President James K. Polk; 15.11.2017

Я исправил проблему с помощью openssl на стороне php и BouncyCastle на стороне C#.
Ключ — это формат pem.

Вот как выглядит 512-битный тестовый ключ:

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

Ключи генерируются с помощью утилиты OpenSSl: www.openssl.org
Вы можете получить пару закрытый/открытый ключ с использованием:

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

512 — размер ключа в битах.

Подпись данных PHP:

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

Проверка подписи С# с использованием только открытого ключа и 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);
}

Легко и просто. Не могу поверить, что потерял несколько дней на этом...
В любом случае спасибо за помощь.

person Brlja    schedule 08.02.2015