Использование SHA1 и RSA с java.security.Signature против MessageDigest и Cipher
Я пытаюсь понять, что делает class Java java.security.Signature . Если я вычисляю дайджест сообщения SHA1, а затем шифрую этот дайджест с помощью RSA, я получаю другой результат, чтобы просить class Signature подписать то же самое:
// Generate new key KeyPair keyPair = KeyPairGenerator.getInstance("RSA").generateKeyPair(); PrivateKey privateKey = keyPair.getPrivate(); String plaintext = "This is the message being signed"; // Compute signature Signature instance = Signature.getInstance("SHA1withRSA"); instance.initSign(privateKey); instance.update((plaintext).getBytes()); byte[] signature = instance.sign(); // Compute digest MessageDigest sha1 = MessageDigest.getInstance("SHA1"); byte[] digest = sha1.digest((plaintext).getBytes()); // Encrypt digest Cipher cipher = Cipher.getInstance("RSA"); cipher.init(Cipher.ENCRYPT_MODE, privateKey); byte[] cipherText = cipher.doFinal(digest); // Display results System.out.println("Input data: " + plaintext); System.out.println("Digest: " + bytes2String(digest)); System.out.println("Cipher text: " + bytes2String(cipherText)); System.out.println("Signature: " + bytes2String(signature));
Результаты (например):
Входные данные: это подписанное сообщение
Дайджест: 62b0a9ef15461c82766fb5bdaae9edbe4ac2e067
Шифрованный текст: 057dc0d2f7f54acc95d3cf5cba9f944619394711003bdd12 …
Подпись: 7177c74bbbb871cc0af92e30d2808ebae146f25d3fd8ba1622 …
У меня должно быть фундаментальное непонимание того, что делает Signature , – я проследил его, и он, похоже, вызывает обновление для объекта MessageDigest , при этом алгоритм установлен на SHA1, как и следовало ожидать, затем получаю дайджест, а затем выполняю шифрование. Чем отличаются результаты?
РЕДАКТИРОВАТЬ:
Леонид заставил меня проверить, должна ли схема подписи делать то, что я думаю. В RFC есть два типа сигнатур:
- RSASSA-PKCS1-v1_5
- RSASSA-PSS
Первый из них (PKCS1) – тот, который я описал выше. Он использует хеш-функцию для создания дайджеста, а затем шифрует результат с помощью закрытого ключа.
Второй алгоритм использует случайное значение соли и является более безопасным, но не детерминированным. Подпись, полученная из приведенного выше кода, не изменяется, если один и тот же ключ используется повторно, поэтому я не думаю, что это может быть PSS.
РЕДАКТИРОВАТЬ:
Вот метод bytes2string
который я использовал:
private static String bytes2String(byte[] bytes) { StringBuilder string = new StringBuilder(); for (byte b : bytes) { String hexString = Integer.toHexString(0x00FF & b); string.append(hexString.length() == 1 ? "0" + hexString : hexString); } return string.toString(); }
- Почему неправильный пароль приводит к тому, что «Заполнение недопустима и не может быть удалено»?
- Шифрование с несколькими разными ключами?
- Шифрование базы данных Android
- Какая лучшая библиотека шифрования в C / C ++?
- Шифровать и расшифровать строку в C #?
- javax.crypto.IllegalBlockSizeException: длина ввода должна быть кратной 16 при расшифровке с дополненным шифрованием
- шифрование sqlite для Android
- Разница между кодированием и шифрованием
Хорошо, я разработал, что происходит. Я был глуп. Леонидас прав, это не просто hash, который зашифрован, это идентификатор hash-алгоритма, объединенного с дайджестом:
DigestInfo ::= SEQUENCE { digestAlgorithm AlgorithmIdentifier, digest OCTET STRING }
Вот почему они разные.
Для получения тех же результатов:
MessageDigest sha1 = MessageDigest.getInstance("SHA1", BOUNCY_CASTLE_PROVIDER); byte[] digest = sha1.digest(content); DERObjectIdentifier sha1oid_ = new DERObjectIdentifier("1.3.14.3.2.26"); AlgorithmIdentifier sha1aid_ = new AlgorithmIdentifier(sha1oid_, null); DigestInfo di = new DigestInfo(sha1aid_, digest); byte[] plainSig = di.getDEREncoded(); Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding", BOUNCY_CASTLE_PROVIDER); cipher.init(Cipher.ENCRYPT_MODE, privateKey); byte[] signature = cipher.doFinal(plainSig);
Несколько более эффективная версия метода bytes2String
private static final char[] hex = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; private static String byteArray2Hex(byte[] bytes) { StringBuilder sb = new StringBuilder(bytes.length * 2); for (final byte b : bytes) { sb.append(hex[(b & 0xF0) >> 4]); sb.append(hex[b & 0x0F]); } return sb.toString(); }
Erm, поняв ваш вопрос: уверены ли вы, что метод подписи создает только SHA1 и шифрует его? GPG и др. Предлагают сжатие / очистку данных. Возможно, этот java-signature-alg также создает отделяемую / прикрепляемую подпись.
Принимая ответ @Mike Houston в качестве указателя, вот полный пример кода, который делает подпись и hash и шифрование.
/** * @param args */ public static void main(String[] args) { try { boolean useBouncyCastleProvider = false; Provider provider = null; if (useBouncyCastleProvider) { provider = new BouncyCastleProvider(); Security.addProvider(provider); } String plainText = "This is a plain text!!"; // KeyPair KeyPairGenerator keyPairGenerator = null; if (null != provider) keyPairGenerator = KeyPairGenerator.getInstance("RSA", provider); else keyPairGenerator = KeyPairGenerator.getInstance("RSA"); keyPairGenerator.initialize(2048); KeyPair keyPair = keyPairGenerator.generateKeyPair(); // Signature Signature signatureProvider = null; if (null != provider) signatureProvider = Signature.getInstance("SHA256WithRSA", provider); else signatureProvider = Signature.getInstance("SHA256WithRSA"); signatureProvider.initSign(keyPair.getPrivate()); signatureProvider.update(plainText.getBytes()); byte[] signature = signatureProvider.sign(); System.out.println("Signature Output : "); System.out.println("\t" + new String(Base64.encode(signature))); // Message Digest String hashingAlgorithm = "SHA-256"; MessageDigest messageDigestProvider = null; if (null != provider) messageDigestProvider = MessageDigest.getInstance(hashingAlgorithm, provider); else messageDigestProvider = MessageDigest.getInstance(hashingAlgorithm); messageDigestProvider.update(plainText.getBytes()); byte[] hash = messageDigestProvider.digest(); DigestAlgorithmIdentifierFinder hashAlgorithmFinder = new DefaultDigestAlgorithmIdentifierFinder(); AlgorithmIdentifier hashingAlgorithmIdentifier = hashAlgorithmFinder.find(hashingAlgorithm); DigestInfo digestInfo = new DigestInfo(hashingAlgorithmIdentifier, hash); byte[] hashToEncrypt = digestInfo.getEncoded(); // Crypto // You could also use "RSA/ECB/PKCS1Padding" for both the BC and SUN Providers. Cipher encCipher = null; if (null != provider) encCipher = Cipher.getInstance("RSA/NONE/PKCS1Padding", provider); else encCipher = Cipher.getInstance("RSA"); encCipher.init(Cipher.ENCRYPT_MODE, keyPair.getPrivate()); byte[] encrypted = encCipher.doFinal(hashToEncrypt); System.out.println("Hash and Encryption Output : "); System.out.println("\t" + new String(Base64.encode(encrypted))); } catch (Throwable e) { e.printStackTrace(); } }
Вы можете использовать BouncyCastle Provider или поставщика Sun по умолчанию.
У меня аналогичная проблема, я тестировал добавление кода и нашел интересные результаты. С помощью этого кода я добавляю, что могу предположить, что в зависимости от используемого «провайдера» фирма может быть другой? (поскольку данные, включенные в шифрование, не всегда равны во всех провайдерах).
Результаты моего теста.
Заключение. Signature Decipher = ??? (trash) + DigestInfo (если мы знаем значение «trash», цифровые подписи будут равны)
IDE Eclipse OUTPUT …
Входные данные: это подписанное сообщение
Дайджест: 62b0a9ef15461c82766fb5bdaae9edbe4ac2e067
DigestInfo: 3021300906052b0e03021a0500041462b0a9ef15461c82766fb5bdaae9edbe4ac2e067
Подпись Расшифруйте: 1ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff003021300906052b0e03021a0500041462b0a9ef15461c82766fb5bdaae9edbe4ac2e067
КОД
import java.security.InvalidKeyException; import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.NoSuchProviderException; import java.security.PrivateKey; import java.security.PublicKey; import java.security.Signature; import java.security.SignatureException; import javax.crypto.BadPaddingException; import javax.crypto.Cipher; import javax.crypto.IllegalBlockSizeException; import javax.crypto.NoSuchPaddingException; import org.bouncycastle.asn1.x509.DigestInfo; import org.bouncycastle.asn1.DERObjectIdentifier; import org.bouncycastle.asn1.x509.AlgorithmIdentifier; public class prueba { /** * @param args * @throws NoSuchProviderException * @throws NoSuchAlgorithmException * @throws InvalidKeyException * @throws SignatureException * @throws NoSuchPaddingException * @throws BadPaddingException * @throws IllegalBlockSizeException */// public static void main(String[] args) throws NoSuchAlgorithmException, NoSuchProviderException, InvalidKeyException, SignatureException, NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException { // TODO Auto-generated method stub KeyPair keyPair = KeyPairGenerator.getInstance("RSA","BC").generateKeyPair(); PrivateKey privateKey = keyPair.getPrivate(); PublicKey puKey = keyPair.getPublic(); String plaintext = "This is the message being signed"; // Hacer la firma Signature instance = Signature.getInstance("SHA1withRSA","BC"); instance.initSign(privateKey); instance.update((plaintext).getBytes()); byte[] signature = instance.sign(); // En dos partes primero hago un Hash MessageDigest digest = MessageDigest.getInstance("SHA1", "BC"); byte[] hash = digest.digest((plaintext).getBytes()); // El digest es identico a openssl dgst -sha1 texto.txt //MessageDigest sha1 = MessageDigest.getInstance("SHA1","BC"); //byte[] digest = sha1.digest((plaintext).getBytes()); AlgorithmIdentifier digestAlgorithm = new AlgorithmIdentifier(new DERObjectIdentifier("1.3.14.3.2.26"), null); // create the digest info DigestInfo di = new DigestInfo(digestAlgorithm, hash); byte[] digestInfo = di.getDEREncoded(); //Luego cifro el hash Cipher cipher = Cipher.getInstance("RSA","BC"); cipher.init(Cipher.ENCRYPT_MODE, privateKey); byte[] cipherText = cipher.doFinal(digestInfo); //byte[] cipherText = cipher.doFinal(digest2); Cipher cipher2 = Cipher.getInstance("RSA","BC"); cipher2.init(Cipher.DECRYPT_MODE, puKey); byte[] cipherText2 = cipher2.doFinal(signature); System.out.println("Input data: " + plaintext); System.out.println("Digest: " + bytes2String(hash)); System.out.println("Signature: " + bytes2String(signature)); System.out.println("Signature2: " + bytes2String(cipherText)); System.out.println("DigestInfo: " + bytes2String(digestInfo)); System.out.println("Signature Decipher: " + bytes2String(cipherText2)); }