DSA генерирует разные подписи с одними и теми же данными

Я использую пример, приведенный в эта статья MSDN о DSACryptoServiceProvider классе. Проблема в том, что каждый раз, когда я запускаю код, я получаю другую подпись.

Я попробовал OpenSSL и не столкнулся с этой проблемой, но на этот раз мне нужно поработать с System.Security.Cryptography.

Это какой-то исходный код:

это хешированное значение, которое нужно подписать

byte[] HashValue =
        {
            59, 4, 248, 102, 77, 97, 142, 201,
            210, 12, 224, 93, 25, 41, 100, 197,
            213, 134, 130, 135
        };

и вот в чем проблема

 // The value to hold the signed value.
 byte[] SignedHashValue1 = DSASignHash(HashValue, privateKeyInfo, "SHA1");
 byte[] SignedHashValue2 = DSASignHash(HashValue, privateKeyInfo, "SHA1");

Я использовал отладчик, чтобы выяснить, SignedHashValue1 не равно SignedHashValue2


Код из статьи:

using System;
using System.Security.Cryptography;

public class DSACSPSample
{
    public static void Main()
    {
        try
        {
            DSAParameters privateKeyInfo;
            DSAParameters publicKeyInfo;

            // Create a new instance of DSACryptoServiceProvider to generate
            // a new key pair.
            using (DSACryptoServiceProvider DSA = new DSACryptoServiceProvider())
            {
                privateKeyInfo = DSA.ExportParameters(true);
                publicKeyInfo = DSA.ExportParameters(false);
            }

            // The hash value to sign.
            byte[] HashValue =
            {
                59, 4, 248, 102, 77, 97, 142, 201,
                210, 12, 224, 93, 25, 41, 100, 197,
                213, 134, 130, 135
            };

            // The value to hold the signed value.
            byte[] SignedHashValue = DSASignHash(HashValue, privateKeyInfo, "SHA1");

            // Verify the hash and display the results.
            bool verified = DSAVerifyHash(HashValue, SignedHashValue, publicKeyInfo, "SHA1");

            if (verified)
            {
                Console.WriteLine("The hash value was verified.");
            }
            else
            {
                Console.WriteLine("The hash value was not verified.");
            }
        }
        catch (ArgumentNullException e)
        {
            Console.WriteLine(e.Message);
        }
    }

    public static byte[] DSASignHash(byte[] HashToSign, DSAParameters DSAKeyInfo,
        string HashAlg)
    {
        byte[] sig = null;

        try
        {
            // Create a new instance of DSACryptoServiceProvider.
            using (DSACryptoServiceProvider DSA = new DSACryptoServiceProvider())
            {
                // Import the key information.
                DSA.ImportParameters(DSAKeyInfo);

                // Create an DSASignatureFormatter object and pass it the
                // DSACryptoServiceProvider to transfer the private key.
                DSASignatureFormatter DSAFormatter = new DSASignatureFormatter(DSA);

                // Set the hash algorithm to the passed value.
                DSAFormatter.SetHashAlgorithm(HashAlg);

                // Create a signature for HashValue and return it.
                sig = DSAFormatter.CreateSignature(HashToSign);
            }
        }
        catch (CryptographicException e)
        {
            Console.WriteLine(e.Message);
        }

        return sig;
    }

    public static bool DSAVerifyHash(byte[] HashValue, byte[] SignedHashValue,
        DSAParameters DSAKeyInfo, string HashAlg)
    {
        bool verified = false;

        try
        {
            // Create a new instance of DSACryptoServiceProvider.
            using (DSACryptoServiceProvider DSA = new DSACryptoServiceProvider())
            {
                // Import the key information.
                DSA.ImportParameters(DSAKeyInfo);

                // Create an DSASignatureDeformatter object and pass it the
                // DSACryptoServiceProvider to transfer the private key.
                DSASignatureDeformatter DSADeformatter = new DSASignatureDeformatter(DSA);

                // Set the hash algorithm to the passed value.
                DSADeformatter.SetHashAlgorithm(HashAlg);

                // Verify signature and return the result.
                verified = DSADeformatter.VerifySignature(HashValue, SignedHashValue);
            }
        }
        catch (CryptographicException e)
        {
            Console.WriteLine(e.Message);
        }

        return verified;
    }
}

person Ibrahim.I    schedule 13.12.2013    source источник
comment
Не перекрестная публикация   -  person CodesInChaos    schedule 14.12.2013


Ответы (2)


Если вы посмотрите, как работает DSA (например, в Википедии), вы увидите, что Первый шаг при генерации подписи - это выбор случайного значения:

Сгенерировать случайное значение k для каждого сообщения, где 0 ‹k‹ q

Позже вы обнаружите, что эта случайность необходима:

В случае DSA критически важны энтропия, секретность и уникальность значения k случайной сигнатуры. Это настолько важно, что нарушение любого из этих трех требований может раскрыть злоумышленнику весь закрытый ключ. Использование одного и того же значения дважды (даже при сохранении k в секрете), использование предсказуемого значения или утечка даже нескольких битов k в каждой из нескольких подписей достаточно, чтобы сломать DSA.

После этого упоминается очень яркий случай нарушения ECDSA (полученный из DSA, но работающий с эллиптическими кривыми).

Поэтому будьте счастливы, что у вас никогда не было одинаковых подписей. В противном случае ваш закрытый ключ был бы под угрозой.

person mkl    schedule 13.12.2013
comment
Есть способы детерминированно генерировать подписи DSA. Основная идея состоит в том, чтобы использовать хэш сообщения и закрытый ключ в качестве k. Лично я предпочитаю эти детерминированные варианты. - person CodesInChaos; 14.12.2013
comment
Есть, да, но вы не можете рассчитывать на алгоритмы, реализованные в какой-либо ОС или SDK для этого. И если они это сделают, их схема может отличаться от вашей. По сути, все, что у вас есть, - это описание алгоритма, который запрашивает случайные числа. - person mkl; 15.12.2013
comment
@CodesInChaos, я не понимаю ключевую вещь для каждого сообщения. Если мы генерируем для каждого сообщения ключ, отличный от закрытого, как тогда можно будет проверять подписи? Если одно и то же сообщение подписано дважды, в результате получается 2 разных хэша, как его можно проверить, используя только один и тот же открытый ключ? смущенный! Пожалуйста помоги! - person fresh learner; 11.07.2017
comment
@IbrahimNadir CodesInChaos не говорит, что для каждого сообщения должен быть свой ключ. Он просто указывает, что случайное значение k для каждого сообщения может быть создано детерминированным способом, пока A этот детерминированный способ не может быть воспроизведен другими и B для разных сообщений вы (почти) всегда получаете разные значения. Хэш закрытого ключа плюс сообщение имеет эти свойства, он не может быть воспроизведен другими пользователями, у которых нет закрытого ключа, а разные сообщения приводят к другим значениям, если только не возникает редких конфликтов. - person mkl; 11.07.2017
comment
@mkl Спасибо за ответ. Я просто хочу уточнить. Допустим, у нас есть одно и то же сообщение, и мы используем два разных случайных значения k для каждого сообщения, чтобы создать два разных хэша одного и того же сообщения. Как они будут проверяться на стороне получателя? Это математика, которая как-то проверяет это, потому что мы отправляем хеш вместе с сообщением. Мы никогда не отправляем значение k для каждого сообщения. Просто неразбериха в голове. Не могли бы вы уточнить? - person fresh learner; 11.07.2017
comment
@IbrahimNadir Не путайте хеши! Хеширование, упомянутое CodesInChaos и объясненное мной, является не хеш-значением подписанным в процессе, это просто метод создания псевдослучайного числа детерминированно на основе некоторого ввода . Хеш подписанный по-прежнему является обычным хешем самого сообщения. - person mkl; 11.07.2017

AFAIK, он каждый раз генерирует новую пару ключей, поэтому подпись должна быть другой, верно?

        // Create a new instance of DSACryptoServiceProvider to generate
        // a new key pair.
        using (DSACryptoServiceProvider DSA = new DSACryptoServiceProvider())
        {
            privateKeyInfo = DSA.ExportParameters(true);
            publicKeyInfo = DSA.ExportParameters(false);
        }

Не следует ли вам сохранять пару ключей и каждый раз загружать одну и ту же пару, чтобы каждый раз добиваться одного и того же результата? см. Как сохранить / получить открытый / закрытый ключ RSA

person Rudi    schedule 13.12.2013
comment
не могли бы вы попробовать добавить byte[] SignedHashValue1 = DSASignHash(HashValue, privateKeyInfo, "SHA1"); после byte[] SignedHashValue = DSASignHash(HashValue, privateKeyInfo, "SHA1"); и сравнить их - person Ibrahim.I; 13.12.2013
comment
Он дает разные подписи в одном сеансе отладки - person Ibrahim.I; 13.12.2013
comment
Я не знаком с алгоритмом DSA, поэтому я не могу сказать, что если задано одно и то же значение HashValue, он все равно сможет проверить для него разные SignedHashValue. - person Rudi; 13.12.2013
comment
Я не так хорошо знаком с DSA, как RSA, но знаю, что некоторые схемы подписи RSA включают случайные данные, поэтому результаты будут всегда разными. - person Duncan Jones; 13.12.2013
comment
DSA (как и ECDSA) требует от пользователя случайного значения во время создания подписи. - person mkl; 13.12.2013