Цифровые подписи, Signature

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

Для работы с цифровой подписью используются асимметричные ключи — автор шифрует сообщение своим закрытым ключом (PrivateKey), а пользователи, имеющие открытые ключи (PublicKey) могут расшифровать и прочитать сообщение. Поскольку закрытый ключ имеет только автор, следовательно зашифровать сообщение мог только он. Этим подтверждается авторство сообщения.

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

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

Цифровая подпись Signature

Сначала рассмотрим процесс формирования и проверки цифровой подписи сообщения в виде дайджеста. Для этого последовательно пройдем 3 этапа :

  1. Генерирование ключей.
  2. Формирование цифровой подписи в виде дайджест сообщения.
  3. Проверка цифровой подписи.

На 2-ом этапе цифровую подпись сообщения в виде дайджеста сохраним в файл, а при проверке цифровой подписи на третьем этапе будем читать этот дайджест из файла и сравнивать его c цифровой подписью, получаемой открытым ключом.

Примечание : пример представлен в исходных кодах в виде юнит-теста JUnitSignature. Более подробную информацию об использовании JUnit можно увидеть на странице Тестирование программы.

1-ый этап. Генерирование ключей, KeyPairGenerator, KeyPair

Не вникая в алгоритмы и особенности их реализациии, можно использовать уже готовые библиотеки и методы криптографии. Они предоставляются определенными так называемыми провайдерами (Provider). По умолчанию встроенные средства Java поставляет провайдер «SUN». Таким образом, единственное, что необходимо сделать для генерирования ключей, это указать алгоритм и провайдера.

Для генерирования пары ключей PrivateKey и PublicKey используются классы KeyPairGenerator и KeyPair пакета java.security. В связи с тем, что большинство криптографических алгоритмов являются вероятностными, необходимо использовать вероятностный источник класса java.security.SecureRandom.

При этом существует возможность использовать разные методы, например, SHA1PRNG (PRNG - pseudo random number generation algorithm). Ниже приводится код генерирования ключей privateKey, publicKey.

KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("DSA", "SUN");
SecureRandom random = SecureRandom.getInstance("SHA1PRNG", "SUN");
keyPairGen.initialize(1024, random);
KeyPair    keyPair    = keyPairGen.genKeyPair();
PrivateKey privateKey = keyPair.getPrivate();
PublicKey  publicKey  = keyPair.getPublic();

Для получения объекта keyPairGen необходимо вызвать static factory метод getInstance(). В качестве аргументов передаются строки с указанием алгоритма DSA (Digital Signature Algorithm) и провайдера «SUN». Провайдера можно было бы и не указывать.

При инициализации объекта KeyPair был определен размер в битах 1024 и источник случайных чисел random. Также можно было бы обойтись и без SecureRandom. На завершающем этапе выполнялась генерация пары ключей generateKeyPair() и определялись значения двух отдельных ключей - методы getPrivate() и getPublic() класса KeyPair.

Для сохранения ключей в файл и чтения из файла предлагаются к использованию простенькие процедуры saveKey(path, key) и readKey(path) :

void saveKey(final String filePath, final Object key) 
                            throws FileNotFoundException, IOException
{
    if (key != null){
        FileOutputStream fos = new FileOutputStream(filePath); 
        ObjectOutputStream oos = new ObjectOutputStream(fos);
        oos.writeObject(key);
        oos.close();
        fos.close();
    }
}

private Object readKey(final String filePath)
                          throws FileNotFoundException, 
                                 IOException, ClassNotFoundException
{
    FileInputStream fis = new FileInputStream(filePath);
    ObjectInputStream ois = new ObjectInputStream(fis);
    Object object = ois.readObject();
    return object;
}

2-ой этап.Формирование цифровой подписи, Signature

Для создания цифровой подписи используется класс Signature и его статический метод getInstance(), которому передается в качестве параметров алгоритм DSA с SHA1 (хэш-функция) и наименование провайдера. После этого signature инициализируется закрытым ключом.

final String MESSAGE = "Пусть всегда будет солнце";	
final String FILE_sign = "data.sign";
	
// Создание подписи
Signature signature = Signature.getInstance("SHA1withDSA", "SUN");
// Инициализация подписи закрытым ключом
signature.initSign(privateKey);

// Формирование цифровой подпись сообщения с закрытым ключом
signature.update(MESSAGE.getBytes());
// Байтовый массив цифровой подписи
byte[] realSignature = signature.sign();

// Сохранение цифровой подписи сообщения в файл
FileOutputStream fos = new FileOutputStream(FILE_sign);
fos.write(realSignature);
fos.close();

После токо, как объект signature подготовлен, формируется цифровая подпись с использованием метода update(), и дайджест цифровой подписи в виде байтового массива сохраняется в файл.

3-ий этап. Проверка цифровой подписи, Signature.verify()

На этом этапе формируется цифровая подпись сообщения с использованием открытого ключа. После этого из файла извлекается цифровая подпись, созданная закрытым ключом, и выполняется проверка методом verify().

// Инициализация цифровой подписи открытым ключом
signature.initVerify(publicKey);
// Формирование цифровой подпись сообщения с открытым ключом
signature.update(MESSAGE.getBytes());
			
// Открытие и чтение цифровой подписи сообщения
FileInputStream fis = new FileInputStream(FILE_sign);
BufferedInputStream bis = new BufferedInputStream(fis);
byte[] bytesSignature = new byte[bis.available()];
bis.read(bytesSignature);
fis.close();

// Проверка цифровой подписи
boolean verified = signature.verify(bytesSignature);
assertTrue("Проверка цифровой подписи", verified);

Цифровая подпись объекта, SignedObject

Рассмотрим пример SigningExample, в котором объект «цифровой подписи» SignedObject будет использован для шифрации текста закрытым ключом, а уже открытым ключом будет извлекаться исходное сообщение. Для этого создадим функцию формирования подписанного объекта createSignedObject и функцию проверки цифрового SignedObject объекта verifySignedObject.

private SignedObject createSignedObject(final String msg, PrivateKey key)
                       throws InvalidKeyException, SignatureException, 
                              IOException, NoSuchAlgorithmException
{
    Signature signature = Signature.getInstance(key.getAlgorithm());
    return new SignedObject(msg, key, signature);
}

private boolean verifySignedObject(final SignedObject obj, PublicKey key)
                       throws InvalidKeyException, SignatureException,
                              NoSuchAlgorithmException
{
    // Verify the signed object
    Signature signature = Signature.getInstance(key.getAlgorithm());
    return obj.verify(key, signature);
}

В следующем листинге приводится код примера, в котором выполняются следующие действия :

  • создаются ключи PrivateKey, PublicKey;
  • ключи сохраняются в файлы;
  • ключи читаются из файлов;
  • создается подписанное объект/сообщение;
  • выполняется проверка подписанного сообщения;
  • извлекается исходный текст сообщения.
private PrivateKey privateKey     = null;
private PublicKey  publicKey      = null;
	
private final String MESSAGE      = "Пусть всегда будет солнце";
private final String FILE_private = "private.key";
private final String FILE_public  = "public.key" ;
	
public SigningExample()
{
    try {
        createKeys();
        saveKey(FILE_private, privateKey);
        saveKey(FILE_public , publicKey );

        privateKey = (PrivateKey) readKey(FILE_private);
        publicKey  = (PublicKey ) readKey(FILE_public );

        SignedObject signedObject = createSignedObject(MESSAGE, privateKey);
        // Проверка подписанного объекта
        boolean verified = verifySignedObject(signedObject, publicKey);
        System.out.println("Проверка подписи объекта : " + verified);

        // Извлечение подписанного объекта
        String unsignedObject = (String) signedObject.getObject();

        System.out.println("Исходный текст объекта : " + unsignedObject);

    } catch (ClassNotFoundException e) {
    } catch (IOException e) {
        System.err.println("Exception thrown during test: " + e.toString());
    } catch (InvalidKeyException e) {
        System.err.println(e.getMessage());
    } catch (SignatureException e) {
        System.err.println(e.getMessage());
    } catch (NoSuchAlgorithmException e) {
        System.err.println(e.getMessage());
    } catch (NoSuchProviderException e) {
        System.err.println(e.getMessage());
    }
}

Примечание : в примерах для формирования цифровой подписи была использована строка «Пусть всегда будет солнце». Однако подписывать можно не только строку, но и объект. При использовании объекта для подписи необходимо выполнять главное условие - объект должен быть сериализован. Более подробная информация о подписывании объекта вместе с примерами представлена на странице Сериализация объектов.

Скачать пример создания ЭЦП

Исходный код рассмотренного примера в виде проекта Eclipse можно скачать здесь (10.8 Kб).

  Рейтинг@Mail.ru