java.security.Signature 대 MessageDigest 및 Cipher와 함께 SHA1 및 RSA 사용
Java java.security.Signature 클래스가 수행 하는 작업을 이해하려고합니다 . SHA1 메시지 다이제스트를 계산 한 다음 RSA를 사용하여 해당 다이제스트를 암호화하면 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));
결과 (예 :) :
입력 데이터 : 서명중인 메시지입니다.
Digest : 62b0a9ef15461c82766fb5bdaae9edbe4ac2e067
암호 텍스트 : 057dc0d2f7f54acc95d3cf5cba9f944619394711003bdd12 ...
서명 : 7177c74bbbb871cc0af92e30d2808ebae146f25d3fd8ba1622 ...
Signature 가 무엇을하는지에 대한 근본적인 오해가 있어야합니다. 저는 그것을 통해 추적했고, 내가 예상 한대로 알고리즘이 SHA1로 설정된 MessageDigest 객체 에 대한 업데이트를 호출하는 것으로 보입니다 . 그런 다음 다이제스트를 얻은 다음 암호화. 결과가 다른 이유는 무엇입니까?
편집하다:
Leonidas는 서명 체계가 내 생각대로해야하는지 확인하도록했습니다. RFC 에는 두 가지 유형의 서명이 정의되어 있습니다 .
이들 중 첫 번째 (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();
}
좋아, 무슨 일인지 알아 냈어. 나는 바보 같았다. Leonidas가 옳습니다. 암호화되는 것은 해시뿐만 아니라 다이제스트와 연결된 해시 알고리즘의 ID입니다.
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);
질문을 이해하신 후 : 서명 방법이 SHA1 만 생성하고 암호화하는 것이 확실한가요? GPG 등은 데이터를 압축 / 명확하게 서명합니다. 아마도이 java-signature-alg는 분리 가능 / 부착 가능한 서명을 생성 할 수도 있습니다.
조금 더 효율적인 버전의 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();
}
@Mike Houston의 답변을 포인터로 취하면 서명 및 해시 및 암호화를 수행하는 완전한 샘플 코드가 있습니다.
/**
* @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 Provider를 사용할 수 있습니다.
비슷한 문제가 발생하여 코드 추가를 테스트하고 흥미로운 결과를 찾았습니다. 이 코드를 추가하면 사용할 "제공자"에 따라 회사가 다를 수 있다고 추론 할 수 있습니다. (암호화에 포함 된 데이터가 모든 공급자에서 항상 같지는 않기 때문입니다).
내 테스트 결과.
결론-서명 해독 = ??? (휴지통) + DigestInfo ( "휴지통"의 값을 안다면 디지털 서명은 동일합니다)
IDE Eclipse 출력 ...
입력 데이터 : 서명중인 메시지입니다.
다이제스트 : 62b0a9ef15461c82766fb5bdaae9edbe4ac2e067
DigestInfo: 3021300906052b0e03021a0500041462b0a9ef15461c82766fb5bdaae9edbe4ac2e067
Signature Decipher: 1ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff003021300906052b0e03021a0500041462b0a9ef15461c82766fb5bdaae9edbe4ac2e067
CODE
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));
}
'program story' 카테고리의 다른 글
| .NET에서 디렉토리 크기를 계산하는 가장 좋은 방법은 무엇입니까? (0) | 2020.11.13 |
|---|---|
| Entity Framework 엔터티 연결을 사용하는 경우 MetadataException (0) | 2020.11.13 |
| PHP : __ ( 'Some text')는 무엇을합니까? (0) | 2020.11.12 |
| 큰 파이썬 프로젝트에서 죽은 코드 찾기 (0) | 2020.11.12 |
| x86_64는 전체 레지스터 내용을 덮어 쓰는 rax / eax / ax / al을 등록합니다. (0) | 2020.11.12 |