Подпись сообщения

Дайджест сообщения подобно дактолоскопическому отпечатку считается уникальным. Если изменить сообщение, то новый цифровой отпечаток будет отличаться от исходного. Поэтому, отправляя раздельно сообщение и его отпечаток, получатель может узнать, было ли сообщение изменено. Но если сообщение и его отпечаток посылаются вместе, то новый отпечаток легко подделать. Дело в том, что алгоритмы создания дайджеста широко известны и для них не нужно использовать секретные ключи. Поэтому, если получателю прислали поддельное сообщение и поддельный цифровой отпечаток, то он никогда не узнает, что сообщение изменено. Решить данную проблему позволяют описываемые ниже способы аутентификации сообщения с помощью цифровой подписи. Аутентификация гарантирует:
  • неизменность сообщения;
  • достоверность отправки сообщения указанным отправителем.

Для того чтобы понять способы работы с цифровыми подписями следует рассмотреть основные принципы шифрования с открытым ключом (public key cryptography), которое основано на использовании пары ключей : открытого (public key) и закрытого (private key). Открытый ключ можно оглашать где угодно и кому угодно, а закрытый необходимо держать в строгом секрете. Значения этих ключей связаны математической зависимостью. Однако для вычисления закрытого ключа на основе известного открытого ключа даже при использовании всей имеющейся компьютерной техники не хватит жизни.

В криптографии используются два типа пар закрытый/открытый ключ : одна пара для шифрования (encryption), а другая пара для аутентификации (authentication). Если сообщение зашифровано открытым ключом, то его можно расшифровать только с помощью соответствующуго ему закрытого ключа. И наоборот, если ваше сообщение подписано с использованием закрытого ключа аутентификации, то любой получатель может проверить достоверность подписи с помощью вашего открытого ключа. Верификацию, (т.е. проверку) пройдут только подписанные ваши сообщения и не смогут пройти другие. Многие криптографические алгоритмы, например RSA и алгоритм цифровой подписи DSA (Digital Signature Algorithm), используют эту идею.Точная структура ключей и строгое соответствие между ключами зависит от конкретного алгоритма.

Как же создается пара ключей? Обычно делается это следующим образом : сначала получают результаты какого-нибудь случайного процесса, а затем используют их в качестве входных данных некоего детерменированного процесса, который и возвращает пару ключей. Программистам совершенно не нужно заботиться о принципе работы данной процедуры, так как это задача математиков и шифровальщиков.

Рассмотрим практический пример использования описанной выше технологии. Предположим, Алиса послала Бобу сообщение, а Боб хочет удостовериться, что сообщение пришло именно от Алисы, а не от какого-нибудь самозванца. Для этого Алиса подписывает (sign) дайджест сообщения с помощью своего закрытого ключа. Боб получает копию открытого ключа Алисы и с его помощью проверяет (verify) подпись. Если проверка прошла успешно, то Боб точно знает, что

  1. Сообщение не было изменено.
  2. Сообщение было послано Алисой, а не кем-то другим.

Закрытый ключ должен быть надежно спрятан. Если он будет перехвачен злоумышленником, то Боб может получить от имени Алисы ложную информацию, которую Алиса на самом деле не отправляла.

Рассмотрим более подробно принцип работы технологии DSA, в которой реализованы три алгоритма.

  • алгоритм генерации пары ключей;
  • алгоритм подписания сообщения;
  • алгоритм верификации подписи.

Пара ключей создается только один раз и может быть использована многократно. Для генерации случайной пары ключей недостаточно класса Random, который основан на текущей дате и времени. Для этого необходимо использовать очень надежные генераторы случайных чисел. Пакет java.util считается недостаточно "криптографически безопасным". Рассмотрим пример. Предположим, что период между двумя "тиками" компьютерного таймера равен 0.1 секунды. Тогда в течение дня может быть сгенерировано приблизительно 865 000 случайных последовательностей. Если злоумышленник точно узнает день генерации псевдослучайного ключа (например, по дате истечения его срока действия), то он сможет достаточно быстро сгенерировать и перебрать все эти случайные числа.

В криптографии для генерации случайных чисел вместо класса Random лучше применить более надежный класс SecureRandom. Для генерации последовательности случайных чисел нужно получить начальное число (seed). Лучше всего это сделать, используя специализированное аппаратное обеспечение, например генератор "белого шума". Еще один способ основан на беспорядочных нажатиях клавиш клавиатуры пользователем. Причем, каждое нажатие клавиши генерирует только один или два бита в начальном числе. После получения всех битов их нужно собрать в массив и передать в качестве параметра методу setSeed.

SecureRandom secrand = new SecureRandom ();
Byte [] b = new byte [20];
// Заполнение массива случайным набором битов
secrand.setSeed (b);

При отсутствии нначального случайного числа генератор случайных чисел вычислит собственное начальное число длиной 20 байт, запуская потоки, переводя их в спящий режим и определяя точное время их активации.

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

После формирования начального числа можно с помощью метода nextBytes () создать последовательность случайно генерируемых байтов.

byte [] randomBytes = new byte [64];
secrand.nextBytes (randomBytes);

На практике для вычисления ключа DSA не нужно самостоятельно создавать случайные значения. Достаточно разместить объект генератора случайных чисел в программе, реализующей алгоритм формирования ключа.

Для создания новой пары ключей используется объект типа KeyPairGenerator, который представляет собой суперкласс для всех алгоритмов генерации пар ключей. Для создания объекта-генератора пары ключей DSA нужно применить метод getInstance () c параметром DSA.

KeyPairGenerator keygen = KeyPairGenerator.getInstance ("DSA");

Возвращаемый объект является объектом класса sun.security.provider.DSAKeyPairGenerator, который, в свою очередь, представляет собой подкласс класса KeyPairGenerator.

Для генерации ключей нужно инициализировать объект-алгоритм, указав мощность (strength) ключа и безопасный генератор случайных чисел. Необходимо отметить, что мощность ключа определяется не длиной сгенерированных ключей, а размером одного из "строительных блоков" ключа. В случае DSA это число битов в модуле - одной из базовых величин, используемых для формирования открытого и закрытого ключей. Предположим, что необходимо генерировать ключи с модулем 512 бит.

SecureRandom secrand = new SecureRandom ();
secrand.setSeed (. . .);
keygen.initialize (512, secrand);

Теперь можно создавать ключи.

KeyPair keys = keygen.generateKeyPair ();
KeyPair morekeys = keygen.generateKeyPair ();

В каждой паре ключей должен быть закрытый и открытый ключ.

PublicKey pubkey = keys.getPublic ();
PrivateKey privkey = keys.getPrivate ();

Чтобы подписать сообщение создается объект Signature, реализующий алгоритм подписи.

Signature sigalg = Signature.getInstance ("DSA");

Данный объект может использоваться как для подписи, так и для верификации сообщения. Чтобы подготовить объект к подписанию сообщения, необходимо вызвать метод initSign () и передать ему закрытый ключ.

sigalg.initSign (privkey);

Теперь, используя метод update (), нужно передать байты объекту Signature так же, как это делалось с дайджестом сообщения.

byte [] bytes = . . .;
while (. . .) signalg.update (bytes);

И, наконец, используя метод sign (), нужно вычислить подпись. Она представляется в виде байтового массива.

byte [] signature = signalg.sign ();

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

Signature verifyalg = Signature.getInstance ("DSA");
verifyalg.initVerify (pubkey);

После этого сообщение следует передать объекту Signature.

byte [] bytes = . . .;
while (. . .) verifyalg.update (bytes);

Теперь подпись можно проверить.

boolean check = verifyalg.verify (signature);

Если метод verify возвращает значение true, это значит, что данное сообщение было подписано соответствующим ему закрытым ключом. Другими словами, это означает аутентификацию сообщения и его отправителя.

Методы java.security.KeyPairGenerator

Метод Описание
static KeyPairGenerator getInstance (String alg) Возвращает объект KeyPairGenerator, который реализует указанный алгоритм. Если заданный алгоритм не поддерживается, то генерируется исключение NoSuchAlgorithmException.
Параметр : alg - наименование алгоритма, например DSA
void initialize (int strength, SecureRandom random) Инициализируется объект KeyPairGenerator.
Параметры :
strength - размер, специфический для конкретного алгоритма; как правило, это количество битов в одном из из параметров, используемых алгоритмом
random - источник случайных значений для генерации ключей
KeyPair generateKeyPair Создает новую пару ключей.

Методы java.security.KeyPair

Метод Описание
PrivateKey getPrivate () Возвращает закрытый ключ пары
PublicKey getPublic () Возвращает открытый ключ пары

Методы java.security.Signature

Метод Описание
static Signature getInstance (String alg) Возвращает объект Signature, который реализует указанный алгоритм. Если заданный алгоритм не поддерживается, то генерируется исключение NoSuchAlgorithmException.
Параметр : alg - наименование алгоритма, например DSA
void initSign (PrivateKey privateKey) Инициализирует данный объект для подписания. Если тип ключа не соответствует типу алгоритма, то генерируется исключение InvalidKeyException.
Параметр : privateKey - закрытый ключ объекта, для которого вычисляется подпись
void update (byte input)
void update (byte [] input)
void update (byte [] input, int offset, int len)
Обновляет буфер сообщения, используя указанные байты.
byte [] sign () Вычисляет и возвращает подпись.
void initVerify (PublicKey publicKey) Инициализирует объект для верификации. Если тип ключа не соответствует типу алгоритма, то генерируется исключение InvalidKeyException.
Параметр : publicKey - открытый ключ для проверяемого объекта
boolean verify (byte [] signature) Проверяет подлинность подписи.