I write the following code for signing the message and then verify it, in java by Bouncy Castle.
signing work properly but verifying not work. the result of code print:
signature tampered
can not recover
and return null.
why eng.hasFullMessage() function return false and why the following code doesn't work?thanks all.
public static String sigVer(PublicKey pu, PrivateKey pr, String original) throws Exception{
//sign
BigInteger big = ((RSAKey) pu).getModulus();
byte[] text = original.getBytes();
RSAKeyParameters rsaPriv = new RSAKeyParameters(true, big,((RSAPrivateKey) pr).getPrivateExponent());
RSAKeyParameters rsaPublic = new RSAKeyParameters(false, big,((RSAPublicKey) pu).getPublicExponent());
RSAEngine rsa = new RSAEngine();
byte[] data;
Digest dig = new SHA1Digest();
ISO9796d2Signer eng = new ISO9796d2Signer(rsa, dig, true);
eng.init(true, rsaPriv);
eng.update(text[0]);
eng.update(text, 1, text.length - 1);
data = eng.generateSignature();
String signature = data.toString();
//verify
eng = new ISO9796d2Signer(rsa, dig, true);
eng.init(false, rsaPublic);
text = signature.getBytes();
if (!eng.verifySignature(text)) {
System.out.println("signature tampered");
}
try{
if (eng.hasFullMessage()) {
eng.updateWithRecoveredMessage(signature.getBytes());
}
byte[] message = eng.getRecoveredMessage();
String ss = message.toString();
return ss;
}
catch (Exception e) {
System.out.println("can not recover");
return null;
}
}
Actually to verify large messages you have to provide the input for verification which is the original message and not only the signature which works only for full recovery
//verify
eng = new ISO9796d2Signer(rsa, dig, true);
eng.init(false, rsaPublic);
// when verifying there has to be also the original plain text
eng.update(text,0,text.length);
signBytes = signature.getBytes();
if (!eng.verifySignature(signBytes)) {
System.out.println("signature tampered");
}
I played around with this method and also receive the errors. In addition the "update" method mentioned does not work and the "updateWithRecoveredMessage" does not exist.
After some testing I figured that:
I have to sue getBytes("UTF-8") and String s = new String(message, "UTF-8")
I can use up to 234 bytes in the signature, with 235 it will break. (guess it would be 256 bytes but some are used for padding/markers)
If I understand correctly the problem is that the signer only allows a certain amount of bytes to be included in the signature.
If you use a longer payload you MUST include the payload in the verification step as well:
eng.init(false, rsaPublic);
byte[] message = payload.getBytes("UTF-8");
eng.update(message, 0, message.length);
then verification works just fine and you get the first part of the payload with eng.getRecoveredMessage().
For me this defeated the purpose, so I went another way.
The workaround I use is to two a two-step approach:
1) I generate a short key which is stored in the signature
2) use a symmetric (i.e. AES) to encrypt the large payload. I also generate a hash for the payload.
3) the key and hash for the payload is the one included in the signature.
Most of the times we don't have the original message to update and verifiy by the signature. On the other hand the length is limited to 234 bytes.
Related
I am creating an RSA signature in Java and sending it in the Auth header to a PHP server which is then verifying it. The problem is that although the signature is being verified in Java, it is failing in PHP. How do I fix this?
private String getSignature(JsonObject body) {
try {
InputStream is = getClass().getClassLoader().getResourceAsStream("private.key");
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
try (InputStreamReader keyReader = new InputStreamReader(is);
PemReader pemReader = new PemReader(keyReader)) {
PemObject pemObject = pemReader.readPemObject();
byte[] content = pemObject.getContent();
PKCS8EncodedKeySpec privKeySpec = new PKCS8EncodedKeySpec(content);
PrivateKey privKey = (RSAPrivateKey) keyFactory.generatePrivate(privKeySpec);
System.out.println(privKey.getAlgorithm());
Signature sign = Signature.getInstance("SHA1withRSA");
sign.initSign(privKey);
sign.update(body.toString().getBytes());
verifySignature(body, sign.sign());
return new String(sign.sign());
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
private String verifySignature(JsonObject body, byte[] bs) {
try {
InputStream is = getClass().getClassLoader().getResourceAsStream("public.pem");
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
try (InputStreamReader keyReader = new InputStreamReader(is);
PemReader pemReader = new PemReader(keyReader)) {
PemObject pemObject = pemReader.readPemObject();
byte[] content = pemObject.getContent();
KeySpec privKeySpec = new X509EncodedKeySpec(content);
PublicKey privKey = (RSAPublicKey) keyFactory.generatePublic(privKeySpec);
System.out.println(privKey.getAlgorithm());
Signature sign = Signature.getInstance("SHA1withRSA");
sign.initVerify(privKey);
sign.update(body.toString().getBytes());
boolean res = sign.verify(bs);
System.out.println(res);
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
My PHP code where I'm verifying the sign:
$fp = fopen("public.pem", "r");
$pub_key = fread($fp, 8192);
fclose($fp);
$pubkeyid = openssl_pkey_get_public($pub_key);
$dd = ["email"=>"kkamran#gmail.com","password"=>"!Pass1234","platform"=>"con"];
$ss = base64_decode("dHIG77+9fu+/ve+/ve+/vVTvv73vv70677+9QiHGnmjHu++/vQYm77+977+977+9bXM+Pe+/ve+/vRtlJcyYY++/vdiu77+9fu+/vUkM77+9RQI2BO+/ve+/vX4YFRwt77+9GO+/vWrvv71F77+9C++/ve+/ve+/ve+/vWTvv73vv71zB++/vXFaQ86277+9LHXvv71+Q3Dvv73vv73vv71TEzMo77+9SgLvv73vv73vv70377+9IO+/vVFW0rYc77+9QX8a77+977+977+977+9Iu+/ve+/ve+/vUYkIU5heO+/vc6e77+977+977+9Rmnvv73vv71+PRxy77+9zLTvv73vv71aDe+/vQjvv71UY++/ve+/vUTvv704FyZjBkB/77+977+9fe+/ve+/ve+/ve+/vXJgUAYUVO+/ve+/ve+/vXNE77+9fmAc77+977+9Ye+/vUAKF8izTO+/ve+/vSPvv700Ru+/ve+/ve+/vWHvv70bDtyoNmxpKd2UKe+/ve+/ve+/vXgCLe+/ve+/vQPvv70p77+9S2Xvv73vv71bX++/vUhw77+9Oe+/vV0+77+9De+/vQs=");
$ok = openssl_verify( $data, $ss, $pubkeyid, OPENSSL_ALGO_SHA1);
Update:
I am now signing like this:
Signature sign = Signature.getInstance("SHA1withRSA");
sign.initSign(privKey);
sign.update(body.toString().getBytes());
String signStr = Base64.getEncoder().encodeToString(sign.sign());
verifySignature(body, signStr.getBytes());
return signStr;
The line:
return new String(sign.sign());
in the getSignature() method performs an decoding of the signature with the default charset. From the posted signature it can be concluded that this is the UTF-8 charset. This UTF-8 decoding corrupts the signature!
Binary data like signatures, ciphertexts, hash values, random binary data etc. generally do not contain UTF-8 compliant byte sequences. When decoding with UTF-8, these non-compliant sequences are replaced by the 0xEFBFBD replacement character, which irreversibly corrupts the data. The 0xEFBFBD byte sequence occurs with high frequency in the posted signature (after Base64 decoding), which is a clear indication of corruption resulting from UTF-8 decoding.
In general, charset encodings such as UTF-8 are not suitable for converting binary data to a string (unless, of course, the binary data was generated using that encoding). Instead, a binary-to-text encoding should be used, e.g. Base64, see also here.
A second problem is the double sign.sign() call in getSignature(). A sign() call resets the state of the signature object to the state immediately after the last initSign(), see here. I.e. in the present case the second call is made without the data from the update() call, so the signature is ultimately created for an empty message. This signature is returned and therefore of course does not correspond to the actual message, so that a later verification will fail.
A possible solution would be to additionally execute the corresponding update() call before the second sign() call.
Of course, in this particular case it is more efficient to execute the sign.sign() call only once and store the result to be able to use it later (as often as needed).
Both problems are successfully fixed as follows:
...
byte[] signature = sign.sign();
verifySignature(body, signature);
return Base64.getEncoder().encodeToString(signature);
...
I am using secp256k1 to sign hash of a text in server and verify the signature on client side .
Now, I need to send actual text as well to the client but my main constraint is bandwidth and I can't add text separately. Therefore, I need to be able to put the actual text inside signature and extract it during verification?
Here is my code
Signing
static final X9ECParameters curve = SECNamedCurves.getByName ("secp256k1");
static final ECDomainParameters domain = new ECDomainParameters(curve.getCurve (), curve.getG (), curve.getN (), curve.getH ());
public byte[] sign (byte[] hash) throws CryptoException
{
if ( priv == null )
{
throw new CryptoException (ErrorCode.KEY_NOT_FOUND, "Need private key to sign");
}
ECDSASigner signer = new ECDSASigner(new HMacDSAKCalculator(new SHA256Digest()));
signer.init (true, new ECPrivateKeyParameters(priv, domain));
BigInteger[] signature = signer.generateSignature (hash);
ByteArrayOutputStream s = new ByteArrayOutputStream();
try
{
DERSequenceGenerator seq = new DERSequenceGenerator(s);
seq.addObject (new ASN1Integer(signature[0]));
seq.addObject (new ASN1Integer(signature[1]));
seq.close ();
return s.toByteArray ();
}
catch ( IOException e )
{
}
return null;
}
Verification
public static boolean verify (byte[] hash, byte[] signature, byte[] pub)
{
ASN1InputStream asn1 = new ASN1InputStream(signature);
try
{
ECDSASigner signer = new ECDSASigner();
signer.init (false, new ECPublicKeyParameters(curve.getCurve ().decodePoint (pub), domain));
DLSequence seq = (DLSequence) asn1.readObject ();
BigInteger r = ((ASN1Integer) seq.getObjectAt (0)).getPositiveValue ();
BigInteger s = ((ASN1Integer) seq.getObjectAt (1)).getPositiveValue ();
return signer.verifySignature (hash, r, s);
}
catch ( Exception e )
{
// threat format errors as invalid signatures
return false;
}
finally
{
try
{
asn1.close ();
}
catch ( IOException e )
{
}
}
}
ECDSA requires a hash over the message to be secure. So all information is lost. Some signature schemes use a hash + part of the message. This is for instance the case for RSA in the ISO/IEC 9796 signature schemes giving (partial) message recovery - which is the technical term of what you (and Dave Thompson) are talking about.
However ECDSA signatures are just big enough to contain a single hash (and usually not even that); there is not much you can do to add data to the hash value. Trying to use EC signatures for partial message recovery is an exercise in futility (also because how verification is performed).
However if you want to use fewer bits you can do still things:
use the most dense representation of message and signature (e.g. just R and S concatenated for ECDSA signatures);
use the smallest key size possible (check https://keylength.com/ for info);
switch to a signature scheme that uses smaller signatures, for instance the BLS signature scheme over elliptic curves - it will halve the signature size compared to ECDSA (using the most efficient encoding).
I've put the BLS scheme last because it is usually not in generic libraries; I consider it the expert way out. You may hit a learning curve.
Signatures themselves don't compress well, by the way, unless you encode them badly. The other disadvantage of compression is that it is usually tricky to calculate a maximum size for all possible messages.
A digital signature, ECDSA or RSA, generally doesn't contain the message (see #dave_thompson_085 comment). An "attached signature" refers to a file that basically contains the message and the digital signature, which can be encoded in a CMS or XMLDsig format.
So if you need to send the message and the signature, just send them together, using a custom or a known format, and compress it additionally to reduce the size
AttachedSignature = zip(format(message + signature))
I am getting information back from Visa Checkout in an encrypted format. The guide on their site provides these instructions:
First, you must decrypt the dynamic key (encKey), then use the decrypted dynamic key value to decrypt the payment data payload (encPaymentData).
Follow these four steps to decrypt the encKey:
Base64-decode the encKey.
Remove the first 32 bytes of the decoded value. This is the HMAC (Hash Message Authentication Code). Calculate a SHA-256 HMAC of the
rest of the decoded data using your API Shared Secret and compare it
to the HMAC from the first 32 bytes.
The next 16 bytes should be removed and used as the IV (Initialization Vector) for the decryption algorithm.
Decrypt the remaining data using AES-256-CBC, the IV from step 3, and the SHA-256 hash of your API Shared Secret.
Follow these four steps to decrypt the encPaymentData using the
decrypted encKey:
Base64-decode the encPaymentData.
Remove the first 32 bytes of the decoded value. This is the HMAC. Calculate a SHA-256 HMAC of the rest of the decoded data using
the
decrypted encKey and compare it with the HMAC from the first 32
bytes.
The next 16 bytes should be removed and used as the IV for the decryption algorithm.
Decrypt the rest of the encPaymentData payload using AES-256-CBC, the IV from step 3, and the SHA256-hash of the
decrypted encKey.
I tried using ColdFusion but I am lost somewhat with the encryption issues, and am unable to fix the code. Below I have what is required. I am stuck on the step 3 & 4 where they say compare it and then decrypt it. Can someone guide what could be done to fix it?
enckey:
2M2WWOD4wANsGwWTmPqQIQYdz9WPwgiR0ntJHGaxm23b5a1sWUtCBcUQUMMtc9hvcYavJ6yqPgETOGZgDOdd9qjDwIb2aV9DLZT1iIcB3zNN5Ddhxd9iiui6TAlJxU/O
encPaymentData:
X2TXp0ZmwHrtfzSP5TPjUOjdZb0rjsHeDSqr8TwIF/VR8sMQhWN5hP4IRhQxWT CZcQhxZoUHP 0g/E/ot sjREAJ8YQf7c7jSzKsXRH/wrew5rQit2wJBlVSSZ YoLeIHbLrTz CfIoFv09hixl7ff27u0YCyV0zjP5vNfCBEIwfqyriqwXK2J QEOxQiKzDUW4br3o1t31aymCQC9eBBpoVKjFfSKlNXM9QEdNZBcLMZ8Wlv8lF/ua bnwshbM9u7Uhudqvut94RZEW NzkRD8MfBo12e/XhnL35qxGpHeQNPClC4EQDK6U/HmegeOj BZLbIIYBs6t9E8Q3AKBwfiPOFgB gSVnhXKnd3nKvllaG BaGrQJtk 7QAtnHMHxQAO5rdiS9465HCdiHa8zlv7SkvWh8EwcKCiT4qiZSM6QuYAeRSzDpPS1gsZ54Q9LizUnueH7yyzSd47cLPd0VlOQxobKtNN2LrsRb3IwOfzuwGnSRf2cNp49hBmmGP1b0BC hhB6UpCqP2ixTPvui NwMYzqZUe336bF1mfnKzEbEZIvIrPyx3uMiLDAns2g7S80gMNnHb/09i49xbfY3V7oudeiHV99FCh67DuG3uHE3/HzIZbcnxJwVJoJj6/3DuzK/Kw1JqSorE0M1qxUqoNkJC4aNCBrqfTlR7/eErrvB554TUZwcyQXqKCwrKv4NJEw6S0n3W1VASkfA0atbJQX2aLgx9kqnhYdDbaU8UcFIoeA45 yEuQ9vXzo2ILQhvamsAAFQd3i4mEOZ KNtMu25dDFlORn5C/oTZ1t1dzJoYMvq44gejp6L3IK e7JCugGchr963a2kd8NFa3wctRDHF8ChHxawVlU0aY7nasrVireMFLiM 9XIb4abfDtct/j1Q8IGN0hRkgCHO6dlnOrAaaQDYYH3axaMDp5Onb04whULYqGbn/XSR8Sn8gnoFbYqVJbR5aCp5Pe9TpfwxlEvV3z8ZfsASqW2y So9gpwg2y16K/FX3Io6kqeqUlAxbTRDfN/ofDIJaO H PUu2teqjvwvCkjPciGOQdXT5JxqoCiHqRwD0zeZPcG3b9Nfrq3Caf6zjwhf /CMeGc3dNHhSkXox R50MP8BlLWk/bXZRScTE/HSrVxE n073utHAnbVOM3gVha0Hr8TmoV8z1vBg5fY253so6kQX61ZIfHneCAZo0qeKRgDgLUryVPmUNc5 yKP8DxtmHl/0YUztpgyEx5njsrn1L 3EHMMUhui8d LQdNZoEpZ9U1Xb7XVsV5gnwR/mOITNOKJQsine4zMMHBcomHclrM0CuI58YrKPqworCmK6CYfzSc8UmXxXUe5dzND/DS9XgqDttQic2/OqTSAK63ynnrNqzr3D56VpDBeDeQjk3mc/0zmuFAPEXoAQoQKfD6HEuajvWJebQ6QIPgA TshqsnPlktbpftr4lsuB1tHS/W8D7SYVFMC/Kxy9QuYWs0cmRTtzfWEKIRHeDElOTQCX5JB5PgzVhhi5kYTi488Ba8j4zvNUw55hEoMxONYO7eMjJosmNjULsT492LGw3EfAgmgx9h3yFLQRZgfylg0h4PfLlcPOAdsnVX9/yLElD xu7Atwc4S7pBWTHvwue7PpRvWpTeqkU5sqiX4KcV5x8rk mBtxm48a8fsmp GNf 4IjwXu9cQaU9WLipiEnkqFsYo7/aAsmmKWBETyQg9BFXYK 165vrzSX8WTsv6ZZDnVjcE1n4Ov8Jl2cnAigoQbB0ROPpIRzZ3zH2diUv1vzlSuh9gbEJf3uQRKlYRVUbpboC0RbQ/7jgznfJAWyLykyDQ0EB8fVEOtbP1l4JEz39QwAU18ph3btnWWuKEV4 ghYvNG4m1DYntSF57s2ajRS6rPtR oYvGjrJL9zbHBhKHlfkIPC0TKotOCi96mqpikbBEfIZSomHxYgDwYCSvt60zaDIjlBxZ1UBdK JL0554Wia9W3Wg91bmYS9Q4SXMT8r4xGYB7OutEV24n7p088rVm/w2SZSiqlLqai539k6WGkzEQf19ytPtIE81a N z7aijTjy 7FCuVPF90svI5/NoGpSINqv84HUcMU71BvXUIT53Ea6CCpiWvvOPpo/XZar44emlIG0UgeB kfP6C6sis=
Secret code:
zRf7WZ3nM7ON{U0E6J5S}KpVm#k2ReDyq#1lG9go
CF Code:
<cfset str = "2M2WWOD4wANsGwWTmPqQIQYdz9WPwgiR0ntJHGaxm23b5a1sWUtCBcUQUMMtc9hvcYavJ6yqPgETOGZgDOdd9qjDwIb2aV9DLZT1iIcB3zNN5Ddhxd9iiui6TAlJxU/O">
<cfset tobas = tobase64(str)>
<cfset getFirst32bytes = Left(tobas,32)>
<cfset tobas2 = RemoveChars(tobas,1,32)>
<cfdump var="#tobas2#">
<cfset key = "zRf7WZ3nM7ON{U0E6J5S}KpVm#k2ReDyq##1lG9go">
<cfset x = hmac("#tobas2#","#key#","HMACSHA256")>
<cfset y = hmac("#getFirst32bytes#","#key#","HMACSHA256")>
<cfset decalgo = Left(x,16)>
<cfset decremainingData = RemoveChars(x,1,16)>
<cfset getDec = Decrypt(decalgo,"#key#","AES")>
<cfdump var="#x#"><br>
<cfdump var="#y#"><br>
<cfdump var="#decalgo#">
<cfdump var="#decremainingData#">
<cfdump var="#getDec#">
This is the java example they have on their site:
private static final String CIPHER_ALGORITHM = "AES/CBC/PKCS5Padding";
private static final String HASH_ALGORITHM = "SHA-256";
private static final String HMAC_ALGORITHM = "HmacSHA256";
private static final int IV_LENGTH = 16, HMAC_LENGTH = 32;
private static final Charset utf8 = Charset.forName("UTF-8");
private static final Provider bcProvider;
static {
bcProvider = new BouncyCastleProvider();
if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) {
Security.addProvider(bcProvider);
}
}
private static byte[] decrypt(byte[] key, byte[] data) throws GeneralSecurityException {
byte[] decodedData = Base64.decode(data);
if (decodedData == null || decodedData.length <= IV_LENGTH) {
throw new RuntimeException("Bad input data.");
}
byte[] hmac = new byte[HMAC_LENGTH];
System.arraycopy(decodedData, 0, hmac, 0, HMAC_LENGTH);
if (!Arrays.equals(hmac,
hmac(key, decodedData, HMAC_LENGTH, decodedData.length– HMAC_LENGTH))) {
throw new RuntimeException("HMAC validation failed.");
}
byte[] iv = new byte[IV_LENGTH];
System.arraycopy(decodedData, HMAC_LENGTH, iv, 0, IV_LENGTH);
Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM, bcProvider);
cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(hash(key), "AES"),
new IvParameterSpec(iv));
return cipher.doFinal(decodedData, HMAC_LENGTH + IV_LENGTH,
decodedData.length– HMAC_LENGTH– IV_LENGTH);
}
private static byte[] hash(byte[] key) throws NoSuchAlgorithmException {
MessageDigest md = MessageDigest.getInstance(HASH_ALGORITHM);
md.update(key);
return md.digest();
}
private static byte[] hmac(byte[] key, byte[] data, int offset, int length)
throws GeneralSecurityException {
Mac mac = Mac.getInstance(HMAC_ALGORITHM, bcProvider);
mac.init(new SecretKeySpec(key, HMAC_ALGORITHM));
mac.update(data, offset, length);
return mac.doFinal();
}
An important thing to understand about the sample code is that it refers to bytes. Your CF code is using characters. That might seem like a trivial distinction, but they are totally different things, which will produce very, very different results. In order to decrypt successfully, you need to work with the bytes (or binary) of the given strings - not characters.
Although it is possible to manipulate binary arrays using core CF functions, like arraySlice(), the syntax gets a little bulky/clunky at times. The reason is that binary arrays are a different type of object than your standard CF array, i.e. byte[] versus java.util.List. So depending on which functions are used, you may need javacast to coerce variables into the expected type. With that in mind ..
Part I - Decrypt the encKey
Base64-decode the encKey.
Remove the first 32 bytes of the decoded value. This is the HMAC (Hash Message Authentication Code). Calculate a SHA-256 HMAC of the
rest of the decoded data using your API Shared Secret and compare it
to the HMAC from the first 32 bytes.
First convert the base64 string into binary using binaryDecode. Then extract the appropriate number of bytes from the returned array. This is the expected HMAC value:
hmacSize = 32;
binaryToDecrypt = binaryDecode(encryptedKey, "base64");
expectedHMAC = binaryEncode( javacast("byte[]", arraySlice(binaryToDecrypt, 1, hmacSize))
, "hex" );
Next, extract all of the remaining bytes, and use them to calculate the actual HMAC. Verify it against the expected value. If the two do not match, something went wrong.
remainData = arraySlice(binaryToDecrypt, hmacSize + 1);
actualHMAC = hmac( javacast("byte[]", remainData ), sharedSecret, "HMACSHA256");
if (compare(actualHMAC, expectedHMAC) != 0) {
throw("ERROR: Invalid HMAC ["& actualHMAC &"]. Expected ["& expectedHMAC &"]");
}
The next 16 bytes should be removed and used as the IV (Initialization Vector) for the decryption algorithm.
The remaining bytes contains an IV, followed by the encrypted value. Before you can decrypt the latter, you need to extract and separate the two:
ivSize = 16;
ivValue = javacast("byte[]", arraySlice(remainData, 1, ivSize));
encryptedValue = javacast("byte[]", arraySlice(remainData, ivSize + 1));
Decrypt the remaining data using AES-256-CBC, the IV from step 3, and the SHA-256 hash of your API Shared Secret.
The last step before you can decrypt is to generate the decryption key, by hashing the shared secret. Unfortunately, CF's hash() function always returns a hex string. So it must be converted into base64 format to be compatible with the decryption function.
keyHex = hash(sharedSecret, "SHA-256", "utf-8");
keyBase64 = binaryEncode(binaryDecode(keyHex, "hex"), "base64");
Finally, use all three values to decrypt. The returned binary will contain the encryption key used in part II.
decryptedKeyBinary = decryptBinary( encryptedValue
, keyBase64
, "AES/CBC/PKCS5Padding"
, ivValue);
Part II - Decrypt the encPaymentData
Use the exact same process as in Part I, just swap the variables:
Use encPaymentData instead of encryptedKey
Use decryptedKeyBinary instead of sharedSecret.
The final, decrypted result will be binary. Use charsetEncode to convert it back into a human readable string:
result = charsetEncode(decryptedResult, "utf-8");
NB: The sample values you posted appear to be broken, as they do not even work with the java example. The steps above do produce the correct result when used valid values (key, data, etcetera).
I want to show that if I modify one bit or byte from a given X509 certificate the signature verification results false (because this modification results different hash value from the certificate). I'm stuck in the case that how to do the modification on the certificate using getTBSCertificate() method. My following code does the verification process perfectly BUT I tried to make it fail using bit or byte modification's idea but it doesn't work. Note that this idea that I proposed is to proof that any modification on the certificate will make a failure while signature verification
public class VerifyX509 {
private static Certificate getCACert;
private static Certificate[] getCert;
public static void main(String[] args) throws CertificateEncodingException {
setURLConnection("https://www.google.com");
X509Certificate x509cert= (X509Certificate) getCert[0];
byte[] b= x509cert.getTBSCertificate();
b[0] = (byte) ~b[0];
// HOW TO UPDATE getTBSCertificate() after flipping the b[0] to make Verify() in my method verifySign() return false!
verifySign();
}
public static void setURLConnection(String link){
try{
int i=1;
URL destinationURL = new URL(link);
HttpsURLConnection con = (HttpsURLConnection) destinationURL.openConnection();
con.connect();
getCert = con.getServerCertificates();
for (Certificate c : getCert)
{
if (i==2)
{
getCACert= c;
return;
}
i+=1;
}
}catch (Exception e1) {
JOptionPane.showMessageDialog(null, "Error while connection! Check your Internet Connection.");
e1.printStackTrace();
}
}
public static boolean verifySign()
{
try
{
getCert[0].verify(getCACert.getPublicKey());
return true;
} catch (GeneralSecurityException e2)
{
return false;
}
}
}
How can I setup proof-of-concept code to show that the verification while fail?
Note that this idea that I proposed is to proof that any modification on the certificate will make a failure while signature verification.
You can demonstrate this (to a certain probability of correctness) by simply flipping random bits in valid certificates and then attempting to validate them.
However, you cannot prove anything like this. A proper proof requires:
A mathematical proof that a properly implement X509 certificate has the property that changing the certificate renders it invalid (with some probability very close to one1).
A formal-methods proof that the code that is loading the certificate and doing the verification is correctly implemented.
1 - In fact, it is easy to see that the probability cannot be exactly one. Apply the pigeonhole principle.
byte[] b= x509cert.getTBSCertificate();
b[0] = (byte) ~b[0];
Changing a byte in an array that you have obtained from the certificate doesn't change the certificate.
You would have to reload it from the byte array using a CertificateFactory.
Mr. Mike, all what you have to do is to get the row data DER-encoded certificate information (TBS part) and you can extract it as below
URL url = new URL("https://www.google.com/");
HttpsURLConnection con = (HttpsURLConnection) url.openConnection();
con.connect();
Certificate userCert[] = con.getServerCertificates();
X509Certificate x509cert = ((X509Certificate) userCert[0]);
byte[] tbs=x509cert.getTBSCertificate();
Then copy the content of the array b to another array bcopy through a loop and do what ever modifications you want (i.e by using the masking technique Anding with x55) after that you can get the hash value through
String sha1 = "";
MessageDigest crypt = MessageDigest.getInstance("SHA-1");
crypt.reset();
crypt.update(bcopy);
sha1 = byteToHex(crypt.digest());
private static String byteToHex(final byte[] hash)
{
Formatter formatter = new Formatter();
for (byte b : hash)
{
formatter.format("%02x", b);
}
String result = formatter.toString();
formatter.close();
return result;
}
at this point you have the hash value of the modified certificate, you can go now and extract the signature from the original certificate [ byte[] sig= x509cert.getSignature(); ] and decrypt the signature to get the hash value and compare it with the modified hash value, good luck ;)
If you look at RFC 5280, the cert has 3 fields:
tbsCertificate
signatureAlgorithm
signatureValue
The signatureValue is the very last item in the certificate.
I had a similar requirement. These are the steps I followed:
Have 1 certificate in .crt format (base-64 encoded) file. The cert is enclosed between "-----BEGIN CERTIFICATE-----" and "-----END CERTIFICATE-----"
Edit the very last character of the certificate on the line just before the END CERTIFICATE line. Just add 1 to that character or reduce by 1. If the last character is x, make it either y or w.
This will change the signature in the certificate and the signature won't be valid anymore.
I want to sign a message and then I get the original message from the signature.
I find RSA & DSA signature in java: http://docs.oracle.com/javase/tutorial/security/apisign/vstep4.html
but their verify method use original data for verify and can not recover message from signature.
Is there any way to do it in Java or any free-library for Java?
thanks all.
You can use BouncyCastle. It supports ISO-9796-2. And ISO9796Test.java provides an example. Here is an excerpt that might help:
RSAKeyParameters pubParameters = new RSAKeyParameters(false, mod3, pub3);
RSAKeyParameters privParameters = new RSAKeyParameters(true, mod3, pri3);
RSAEngine rsa = new RSAEngine();
byte[] data;
ISO9796d2Signer eng = new ISO9796d2Signer(rsa, new RIPEMD128Digest());
eng.init(true, privParameters);
eng.update(msg4[0]);
eng.update(msg4, 1, msg4.length - 1);
data = eng.generateSignature();
eng.init(false, pubParameters);
eng.update(msg4[0]);
eng.update(msg4, 1, msg4.length - 1);
if (eng.hasFullMessage()) {
eng = new ISO9796d2Signer(rsa, new RIPEMD128Digest());
eng.init(false, pubParameters);
if (!eng.verifySignature(sig4)) {
// signature tampered with
}
byte[] message = eng.getRecoveredMessage(), 0, msg4);
}
I suppose you are referring to ISO9796-2 digital signature scheme 1, trailer field 1, octet "BC" that doesn't require original message.
RSAEngine rsa = new RSAEngine();
//DS as SHA1
Digest dig = new SHA1Digest();
//3rd parameter "true" indicates implicit which means TF1(octet BC)
ISO9796d2Signer eng = new ISO9796d2Signer(rsa, dig, true);
eng.init(false, pubKeyParameter);
eng.verifySignature(sig); // if the signature is valid
eng.getRecoveredMessage(); // get the recovered message, only after successful signature verification