I need to verify an ECDSA signature with Java using BouncyCastle crypto provider. So far BouncyCastle failed to verify the signature.
The signature is created in Atmel AT88CK590 Crypto Authentication module and Public key can be obtained out from the module. Here is the Public Key, in C/C++ format, of 64 octets long:
uint8_t pubKey[] = {
// X coordinate of the elliptic curve.
0xc1, 0x71, 0xCB, 0xED, 0x65, 0x71, 0x82, 0x2E, 0x8F, 0x8A, 0x43, 0x8D, 0x72, 0x56, 0xD1, 0xC8,
0x86, 0x3C, 0xD0, 0xBC, 0x7F, 0xCC, 0xE3, 0x6D, 0xE7, 0xB7, 0x17, 0xED, 0x29, 0xC8, 0x38, 0xCB,
// Y coordinate of the elliptic curve.
0x80, 0xCD, 0xBE, 0x0F, 0x1D, 0x5C, 0xC5, 0x46, 0x99, 0x24, 0x8F, 0x6E, 0x0A, 0xEA, 0x1F, 0x7A,
0x43, 0xBA, 0x2B, 0x03, 0x80, 0x90, 0xE9, 0x25, 0xB2, 0xD0, 0xE6, 0x48, 0x93, 0x91, 0x64, 0x83
};
The original message, the signature and Public key in Base64 encoding:
// Raw message to sign
private static final String tokenStr = "12345678901234567890123456789012";
// Base64 encoded
private static final String pubKeyStr = "wXHL7WVxgi6PikONclbRyIY80Lx/zONt57cX7SnIOMuAzb4PHVzFRpkkj24K6h96
Q7orA4CQ6SWy0OZIk5Fkgw==";
// Base64 encoded
private static final String signatureStr = "XF2WossFTA82ndYFGEH0FPqAldkFQLGd/Bv/Qh8UYip7sXUvCUnFgi1YXjN3WxLn
IwSo3OaHLCOzGAtIis0b3A==";
To convert the Public key I am using the following:
private static PublicKey getPublicKeyFromBytes(byte[] pubKey, String ecSpec, String provider)
throws NoSuchAlgorithmException, InvalidKeySpecException, NoSuchProviderException {
ECNamedCurveParameterSpec spec = ECNamedCurveTable.getParameterSpec(ecSpec);
KeyFactory kf = KeyFactory.getInstance(ECDSA_CRYPTO, provider);
ECNamedCurveSpec params = new ECNamedCurveSpec(ecSpec, spec.getCurve(), spec.getG(), spec.getN());
ECPoint pubPoint = ECPointUtil.decodePoint(params.getCurve(), pubKey);
ECPublicKeySpec pubKeySpec = new ECPublicKeySpec(pubPoint, params);
PublicKey publicKey = kf.generatePublic(pubKeySpec);
return publicKey;
}
To convert the signature to DER format I am using the following:
private static byte[] toDERSignature(byte[] tokenSignature) throws IOException {
byte[] r = Arrays.copyOfRange(tokenSignature, 0, tokenSignature.length / 2);
byte[] s = Arrays.copyOfRange(tokenSignature, tokenSignature.length / 2, tokenSignature.length);
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
DEROutputStream derOutputStream = new DEROutputStream(byteArrayOutputStream);
ASN1EncodableVector v = new ASN1EncodableVector();
v.add(new ASN1Integer(new BigInteger(1, r)));
v.add(new ASN1Integer(new BigInteger(1, s)));
derOutputStream.writeObject(new DERSequence(v));
byte[] derSignature = byteArrayOutputStream.toByteArray();
return derSignature;
}
Here is the code to verify the signature:
Security.addProvider(new BouncyCastleProvider());
byte[] tokenBytes = tokenStr.getBytes("UTF-8");
String urlDecodePubKeyStr = pubKeyStr.replace(newlineHtml, "");
byte[] pubKeyBytes = DatatypeConverter.parseBase64Binary(urlDecodePubKeyStr);
String urlDecodeSignatureStr = signatureStr.replace(newlineHtml, "");
byte[] signBytes = DatatypeConverter.parseBase64Binary(urlDecodeSignatureStr);
byte[] derSignature = toDERSignature(signBytes);
ByteBuffer bb = ByteBuffer.allocate(pubKeyBytes.length + 1);
bb.put((byte)4);
bb.put(pubKeyBytes);
PublicKey ecPublicKey = getPublicKeyFromBytes(bb.array(), "prime256v1", "BC");
System.out.println("\nSignature: " + Hex.toHexString(signBytes).toUpperCase());
System.out.println("DER Signature: " + Hex.toHexString(derSignature).toUpperCase());
System.out.println(ecPublicKey.toString());
Signature signature = Signature.getInstance("SHA256withECDSA", "BC");
signature.initVerify(ecPublicKey);
signature.update(tokenBytes);
boolean result = signature.verify(derSignature);
System.out.println("BC Signature Valid: " + result);
The output:
Signature: 5C5D96A2CB054C0F369DD6051841F414FA8095D90540B19DFC1BFF421F14622A7BB1752F0949C5822D585E33775B12E72304A8DCE6872C23B3180B488ACD1BDC
DER Signature: 304402205C5D96A2CB054C0F369DD6051841F414FA8095D90540B19DFC1BFF421F14622A02207BB1752F0949C5822D585E33775B12E72304A8DCE6872C23B3180B488ACD1BDC
EC Public Key
X: c171cbed6571822e8f8a438d7256d1c8863cd0bc7fcce36de7b717ed29c838cb
Y: 80cdbe0f1d5cc54699248f6e0aea1f7a43ba2b038090e925b2d0e64893916483
BC Signature Valid: false
Have anyone run into the same problem before? What did I miss here?
That signature value is not computed on the hash as is standard and usual, but directly on the data. Either:
the Atmel doesn't hash so your could must hash first and supply the hash otuput to be signed
the Atmel should hash but you didn't set some necessary option or something
you actually want an unconventional 'raw' signature with no hash. Note this limits the data to 32 or maybe 31 bytes. And if the values to be signed can be affected by an adversary and the random generator used for signing is weak, I think that gives an attack that recovers your privatekey (although I haven't worked through it). Even with the if's that's a risk I would avoid.
At any rate, the value you have can be verified using scheme NoneWithECDSA.
In addition, in testing this I found some possible improvements in your code that you may or may not want. First, you don't need to quasi-encode and then decode the point, given you have X,Y in fixed format you can just use them. Second, you do need to DER-encode the signature, but it can be done more simply. Here's my version, with both changes, in a single linear block for clarity:
public static void main (String[] args) throws Exception {
Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
// test data; real data would come from outside
byte[] dataBytes = "12345678901234567890123456789012".getBytes("UTF-8");
String sigString = "XF2WossFTA82ndYFGEH0FPqAldkFQLGd/Bv/Qh8UYip7sXUvCUnFgi1YXjN3WxLn
IwSo3OaHLCOzGAtIis0b3A==";
String pubkeyString = "wXHL7WVxgi6PikONclbRyIY80Lx/zONt57cX7SnIOMuAzb4PHVzFRpkkj24K6h96
Q7orA4CQ6SWy0OZIk5Fkgw==";
String ecSpec="prime256v1"; int size=32; // bytes for x,y in pubkey also r,s in sig
byte[] pubkeyBytes = DatatypeConverter.parseBase64Binary(pubkeyString.replaceAll("
","") );
KeyFactory kf = KeyFactory.getInstance ("ECDSA", "BC");
ECNamedCurveParameterSpec cspec = ECNamedCurveTable.getParameterSpec(ecSpec);
BigInteger x = new BigInteger(1, Arrays.copyOfRange(pubkeyBytes,0,size));
BigInteger y = new BigInteger(1, Arrays.copyOfRange(pubkeyBytes,size,size*2));
ECPublicKeySpec kspec = new ECPublicKeySpec (cspec.getCurve().createPoint(x, y), cspec);
PublicKey k = kf.generatePublic(kspec);
byte[] sigBytes = DatatypeConverter.parseBase64Binary(sigString.replaceAll("
","") );
ASN1EncodableVector v = new ASN1EncodableVector();
v.add(/*r*/new ASN1Integer(new BigInteger(1, Arrays.copyOfRange(sigBytes,0,size))));
v.add(/*s*/new ASN1Integer(new BigInteger(1, Arrays.copyOfRange(sigBytes,size,size*2))));
byte[] sigDer = new DERSequence(v).getEncoded();
Signature sig = Signature.getInstance("NoneWithECDSA", "BC"); // NOTE None instead of a hash
sig.initVerify (k); sig.update (dataBytes);
System.out.println ("verify="+sig.verify(sigDer));
}
PS: you comment the publickey halves as X [Y] coordinate of the elliptic curve. They are coordinates of the point on the curve which is the publickey; the curve itself doesn't have coordinates.
Related
In my code I am using hardcoded arrays(given below) for IV and key
**private static byte[] IVAes = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16 };
private static byte[] keyAes = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16 };
public static String encryptAes(String strPlain) {
byte[] encrypted = null;
if (StringUtils.isBlank(strPlain)) {
return strPlain;
}
byte[] toEncrypt = strPlain.getBytes();
try {
AlgorithmParameterSpec paramSpec = new IvParameterSpec(IVAes);
// Generate the key specs.
SecretKeySpec skeySpec = new SecretKeySpec(keyAes, AES_ALGORITHM);
// Instantiate the cipher
Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
cipher.init(Cipher.ENCRYPT_MODE, skeySpec, paramSpec);
encrypted = cipher.doFinal(toEncrypt);
} catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException
| InvalidAlgorithmParameterException | IllegalBlockSizeException | BadPaddingException e) {
LOGGER.error(e.getMessage(), e);
}
return new String(Base64.encodeBase64(encrypted));
}**
but using hardcoded array as IV and Key is not prefered due to security perspective. Instead of this type of Hardcoded array can I use SecureRandom() as given below-
**public static String encryptAes(String strPlain) {
byte[] encrypted = null;
if (StringUtils.isBlank(strPlain)) {
return strPlain;
}
byte[] toEncrypt = strPlain.getBytes();
try {
//---------calling generateIV method
AlgorithmParameterSpec paramSpec = generateIv();
// Instantiate the cipher
Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM);
cipher.init(Cipher.ENCRYPT_MODE, skeySpec, paramSpec);
encrypted = cipher.doFinal(toEncrypt);
} catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException
| InvalidAlgorithmParameterException | IllegalBlockSizeException | BadPaddingException e) {
LOGGER.error(e.getMessage(), e);
}
return new String(Base64.encodeBase64(encrypted));
}
public static IvParameterSpec generateIv() {
byte[] IVAes = new byte[16];
new SecureRandom().nextBytes(IVAes);
return new IvParameterSpec(IVAes);
}
int n = 128;
public static SecretKey generateKey(int n) throws NoSuchAlgorithmException {
KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
keyGenerator.init(n);
SecretKey key = keyGenerator.generateKey();
return key;
}**
I just wanted to know that creating array of 16 bytes for IV and key by using SecureRandom and also key generator will give same result as it was giving when I use hardcoded array as shown above??
The whole idea of a cipher is that it is indistinguishable from random (not considering the length of the ciphertext, that obviously depends on the size of the message in some way). If you have a unique key and IV then the ciphertext should be indistinguishable from random, although for CBC the IV should also be randomized. If you want to test if it "works" then you'll have to decrypt the ciphertext. Otherwise you can only check if the ciphertext size is about right.
However, if you are asking about the security of your code then sure, the use of SecureRandom and KeyGenerator seems fine. You might want to prefix the IV to the ciphertext, that's the common way to communicate a random IV. If you want to know how to distribute keys then I'd suggest reading into symmetric key management; that depends on the use case.
For my fellow crypto nerds, yes, sure related key attacks are a thing so having a unique key & IV is not entirely the whole story, but these keys are randomized, so that's fine. There are probably some other theoretical / practical issues with the answer which can usually be ignored.
I have an RSA public key certificate. I can use the file that has a .PEM extension or simply use it as a String which has the following format:
-----BEGIN RSA PUBLIC KEY-----
{KEY}
-----END RSA PUBLIC KEY-----
I am trying to use this key in order to send an encrypted JSON to the server. I tried numerous solutions from other related stack overflow questions, but none of the answers didn't for me. This answer seems to makes sense https://stackoverflow.com/a/43534042, but something doesn't work properly, maybe it's because X509EncodedKeySpec expects DER encoded data not PEM according to one of the comments. But in this case what should i use for PEM encoded data?
As already commented by #Topaco your RSA Public Key is in PEM encoding but in PKCS#1 format and not in PKCS#8 format that is readable by Java "out of the box".
The following solution was provided by #Maarten Bodewes here on SO (https://stackoverflow.com/a/54246646/8166854) will do the job to read and transform it to a (Java) usable RSAPublicKey.
The solution is running on my OpenJdk11, if your'e using "Android Java" you may have to change the Base64-calls. There are no external libraries like Bouncy Castle necessary. Please obey the notes from Maarten regarding key lengths.
The simple output:
Load RSA PKCS#1 Public Keys
pkcs1PublicKey: Sun RSA public key, 2048 bits
params: null
modulus: 30333480050529072539152474433261825229175303911986187056546130987160889422922632165228273249976997833741424393377152058709551313162877595353675051556949998681388601725684016724167050111037861889500002806879899578986908702627237884089998121288607696752162223715667435607286689842713475938751449494999920670300421827737208147069624343973533326291094315256948284968840679921633097541211738122424891429452073949806872319418453594822983237338545978675594260211082913078702997218079517998196340177653632261614031770091082266225991043014081642881957716572923856737534043425399435601282335538921977379429228634484095086075971
public exponent: 65537
code:
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
public class LoadPkcs1PublicKeyPemSo {
// solution from https://stackoverflow.com/a/54246646/8166854 answered Jan 18 '19 at 1:36 Maarten Bodewes
private static final int SEQUENCE_TAG = 0x30;
private static final int BIT_STRING_TAG = 0x03;
private static final byte[] NO_UNUSED_BITS = new byte[] { 0x00 };
private static final byte[] RSA_ALGORITHM_IDENTIFIER_SEQUENCE =
{(byte) 0x30, (byte) 0x0d,
(byte) 0x06, (byte) 0x09, (byte) 0x2a, (byte) 0x86, (byte) 0x48, (byte) 0x86, (byte) 0xf7, (byte) 0x0d, (byte) 0x01, (byte) 0x01, (byte) 0x01,
(byte) 0x05, (byte) 0x00};
public static void main(String[] args) throws GeneralSecurityException, IOException {
System.out.println("Load RSA PKCS#1 Public Keys");
String rsaPublicKeyPem = "-----BEGIN RSA PUBLIC KEY-----\n" +
"MIIBCgKCAQEA8EmWJUZ/Osz4vXtUU2S+0M4BP9+s423gjMjoX+qP1iCnlcRcFWxt\n" +
"hQGN2CWSMZwR/vY9V0un/nsIxhZSWOH9iKzqUtZD4jt35jqOTeJ3PCSr48JirVDN\n" +
"Let7hRT37Ovfu5iieMN7ZNpkjeIG/CfT/QQl7R+kO/EnTmL3QjLKQNV/HhEbHS2/\n" +
"44x7PPoHqSqkOvl8GW0qtL39gTLWgAe801/w5PmcQ38CKG0oT2gdJmJqIxNmAEHk\n" +
"atYGHcMDtXRBpOhOSdraFj6SmPyHEmLBishaq7Jm8NPPNK9QcEQ3q+ERa5M6eM72\n" +
"PpF93g2p5cjKgyzzfoIV09Zb/LJ2aW2gQwIDAQAB\n" +
"-----END RSA PUBLIC KEY-----";
RSAPublicKey pkcs1PublicKey = getPkcs1PublicKeyFromString(rsaPublicKeyPem);
System.out.println("pkcs1PublicKey: " + pkcs1PublicKey);
}
public static RSAPublicKey getPkcs1PublicKeyFromString(String key) throws GeneralSecurityException {
String publicKeyPEM = key;
publicKeyPEM = publicKeyPEM.replace("-----BEGIN RSA PUBLIC KEY-----", "");
publicKeyPEM = publicKeyPEM.replace("-----END RSA PUBLIC KEY-----", "");
publicKeyPEM = publicKeyPEM.replaceAll("[\\r\\n]+", "");
byte[] pkcs1PublicKeyEncoding = Base64.getDecoder().decode(publicKeyPEM);
return decodePKCS1PublicKey(pkcs1PublicKeyEncoding);
}
/*
solution from https://stackoverflow.com/a/54246646/8166854 answered Jan 18 '19 at 1:36 Maarten Bodewes
The following code turns a PKCS#1 encoded public key into a SubjectPublicKeyInfo encoded public key,
which is the public key encoding accepted by the RSA KeyFactory using X509EncodedKeySpec -
as SubjectPublicKeyInfo is defined in the X.509 specifications.
Basically it is a low level DER encoding scheme which
wraps the PKCS#1 encoded key into a bit string (tag 0x03, and a encoding for the number of unused
bits, a byte valued 0x00);
adds the RSA algorithm identifier sequence (the RSA OID + a null parameter) in front -
pre-encoded as byte array constant;
and finally puts both of those into a sequence (tag 0x30).
No libraries are used. Actually, for createSubjectPublicKeyInfoEncoding, no import statements are even required.
Notes:
NoSuchAlgorithmException should probably be caught and put into a RuntimeException;
the private method createDERLengthEncoding should probably not accept negative sizes.
Larger keys have not been tested, please validate createDERLengthEncoding for those -
I presume it works, but better be safe than sorry.
*/
public static RSAPublicKey decodePKCS1PublicKey(byte[] pkcs1PublicKeyEncoding)
throws NoSuchAlgorithmException, InvalidKeySpecException
{
byte[] subjectPublicKeyInfo2 = createSubjectPublicKeyInfoEncoding(pkcs1PublicKeyEncoding);
KeyFactory rsaKeyFactory = KeyFactory.getInstance("RSA");
RSAPublicKey generatePublic = (RSAPublicKey) rsaKeyFactory.generatePublic(new X509EncodedKeySpec(subjectPublicKeyInfo2));
return generatePublic;
}
public static byte[] createSubjectPublicKeyInfoEncoding(byte[] pkcs1PublicKeyEncoding)
{
byte[] subjectPublicKeyBitString = createDEREncoding(BIT_STRING_TAG, concat(NO_UNUSED_BITS, pkcs1PublicKeyEncoding));
byte[] subjectPublicKeyInfoValue = concat(RSA_ALGORITHM_IDENTIFIER_SEQUENCE, subjectPublicKeyBitString);
byte[] subjectPublicKeyInfoSequence = createDEREncoding(SEQUENCE_TAG, subjectPublicKeyInfoValue);
return subjectPublicKeyInfoSequence;
}
private static byte[] concat(byte[] ... bas)
{
int len = 0;
for (int i = 0; i < bas.length; i++)
{
len += bas[i].length;
}
byte[] buf = new byte[len];
int off = 0;
for (int i = 0; i < bas.length; i++)
{
System.arraycopy(bas[i], 0, buf, off, bas[i].length);
off += bas[i].length;
}
return buf;
}
private static byte[] createDEREncoding(int tag, byte[] value)
{
if (tag < 0 || tag >= 0xFF)
{
throw new IllegalArgumentException("Currently only single byte tags supported");
}
byte[] lengthEncoding = createDERLengthEncoding(value.length);
int size = 1 + lengthEncoding.length + value.length;
byte[] derEncodingBuf = new byte[size];
int off = 0;
derEncodingBuf[off++] = (byte) tag;
System.arraycopy(lengthEncoding, 0, derEncodingBuf, off, lengthEncoding.length);
off += lengthEncoding.length;
System.arraycopy(value, 0, derEncodingBuf, off, value.length);
return derEncodingBuf;
}
private static byte[] createDERLengthEncoding(int size)
{
if (size <= 0x7F)
{
// single byte length encoding
return new byte[] { (byte) size };
}
else if (size <= 0xFF)
{
// double byte length encoding
return new byte[] { (byte) 0x81, (byte) size };
}
else if (size <= 0xFFFF)
{
// triple byte length encoding
return new byte[] { (byte) 0x82, (byte) (size >> Byte.SIZE), (byte) size };
}
throw new IllegalArgumentException("size too large, only up to 64KiB length encoding supported: " + size);
}
}
Michael Fehr's answer shows how to load a public key in PKCS#1 format without third party libraries. If the latter is a requirement, then that's the way you have to go.
Otherwise, if you use BouncyCastle, there are much less elaborate solutions that might also be considered and are therefore worth mentioning (although the given answer is already accepted):
The following method expects a public key in PKCS#1 format, PEM encoded and loads it into a java.security.PublicKey instance:
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
import org.bouncycastle.openssl.PEMParser;
import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;
import java.security.PublicKey;
import java.io.StringReader;
...
private static PublicKey getPublicKey(String publicKeyc) throws Exception {
PEMParser pemParser = new PEMParser(new StringReader(publicKeyc));
JcaPEMKeyConverter jcaPEMKeyConverter = new JcaPEMKeyConverter();
SubjectPublicKeyInfo subjectPublicKeyInfo = (SubjectPublicKeyInfo)pemParser.readObject();
PublicKey publicKey = jcaPEMKeyConverter.getPublicKey(subjectPublicKeyInfo);
return publicKey;
}
Another similarly compact implementation can be found here.
To use BouncyCastle in Android, a dependency to BouncyCastle corresponding to the API level used must be referenced in the dependencies section of the build.gradle file. I used API Level 28 (Pie) and referenced the following dependency (assuming Android Studio):
implementation 'org.bouncycastle:bcpkix-jdk15on:1.65'
Currently I'm working on a project where they use AES encryption with RFC2898 derived bytes. This the decryption method that I've provided. Now I need to implement it in java.
private string Decrypt(string cipherText)
{
string EncryptionKey = "MAKV2SPBNI657328B";
cipherText = cipherText.Replace(" ", "+");
byte[] cipherBytes = Convert.FromBase64String(cipherText);
using (Aes encryptor = Aes.Create())
{
Rfc2898DeriveBytes pdb = new Rfc2898DeriveBytes(EncryptionKey, new byte[] {
0x49, 0x76, 0x61, 0x6e, 0x20, 0x4d, 0x65, 0x64, 0x76, 0x65, 0x64, 0x65, 0x76
});
encryptor.Key = pdb.GetBytes(32);
encryptor.IV = pdb.GetBytes(16);
using (MemoryStream ms = new MemoryStream())
{
using (CryptoStream cs = new CryptoStream(ms, encryptor.CreateDecryptor(), CryptoStreamMode.Write))
{
cs.Write(cipherBytes, 0, cipherBytes.Length);
cs.Close();
}
cipherText = Encoding.Unicode.GetString(ms.ToArray());
}
}
return cipherText;
}
This is what I go so far:
String EncryptionKey = "MAKV2SPBNI657328B";
String userName="5L9p7pXPxc1N7ey6tpJOla8n10dfCNaSJFs%2bp5U0srs0GdH3OcMWs%2fDxMW69BQb7";
byte[] salt = new byte[] {0x49, 0x76, 0x61, 0x6e, 0x20, 0x4d, 0x65, 0x64, 0x76, 0x65, 0x64, 0x65, 0x76};
try {
userName = java.net.URLDecoder.decode(userName, StandardCharsets.UTF_8.name());
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
PBEKeySpec pbeKeySpec = new PBEKeySpec(EncryptionKey.toCharArray(), salt, 1000);
Key secretKey = factory.generateSecret(pbeKeySpec);
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, secretKey);
byte[] result = cipher.doFinal(userName.getBytes("UTF-8"));
System.out.println(result.toString());
} catch (Exception e) {
System.out.println(e.getMessage());
}
But I'm getting errors as below:
Key length not found
java.security.spec.InvalidKeySpecException: Key length not found
There are some issues in the Java code: The number of bits to be generated must be specified, besides the key the IV must be derived, the IV must be applied for decryption, the ciphertext must be Base64 decoded and Utf-16LE must be used when decoding the plaintext. In detail:
When instantiating PBEKeySpec, the number of bits to be generated must be specified in the 4th parameter. Since both, key (256 bits) and IV (128 bits) are derived in the C# code, 384 (= 256 + 128) must be applied:
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
PBEKeySpec pbeKeySpec = new PBEKeySpec(encryptionKey.toCharArray(), salt, 1000, 384);
The first 256 bits are the key, the following 128 bits the IV:
byte[] derivedData = factory.generateSecret(pbeKeySpec).getEncoded();
byte[] key = new byte[32];
byte[] iv = new byte[16];
System.arraycopy(derivedData, 0, key, 0, key.length);
System.arraycopy(derivedData, key.length, iv, 0, iv.length);
The IV must be passed in the 3rd parameter of the Cipher#init-call using an IvParameterSpec instance:
SecretKeySpec secretKeySpec = new SecretKeySpec(key, "AES");
IvParameterSpec ivSpec = new IvParameterSpec(iv);
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, ivSpec);
The ciphertext must be Base64-decoded before it can be decrypted:
byte[] result = cipher.doFinal(Base64.getDecoder().decode(userName));
From the decrypted byte array a string has to be created using Utf-16LE encoding (which corresponds to Encoding.Unicode in C#):
System.out.println(new String(result, StandardCharsets.UTF_16LE)); // Output: Mohammad Al Mamun
Note that for CBC mode, it's important that a key/IV combination is only used once for security reasons. For the C# (or Java) code here, this means that for the same password, different salts must be used for each encryption, see here.
I am using following code but It's not decrypting the text properly, what am I getting as output is
ciphered: %öNo2F?¢¶SHºûÅ“?¾
plaintext: hello × am originÎl
public static void main(String[] args) throws Exception {
// TODO Auto-generated method stub
// Dernier exemple CTR mode
// Clé 16 bits
byte[] keyBytes = new byte[] { (byte) 0x36, (byte) 0xf1, (byte) 0x83,
(byte) 0x57, (byte) 0xbe, (byte) 0x4d, (byte) 0xbd,
(byte) 0x77, (byte) 0xf0, (byte) 0x50, (byte) 0x51,
(byte) 0x5c, 0x73, (byte) 0xfc, (byte) 0xf9, (byte) 0xf2 };
// IV 16 bits (préfixe du cipherText)
byte[] ivBytes = new byte[] { (byte) 0x69, (byte) 0xdd, (byte) 0xa8,
(byte) 0x45, (byte) 0x5c, (byte) 0x7d, (byte) 0xd4,
(byte) 0x25, (byte) 0x4b, (byte) 0xf3, (byte) 0x53,
(byte) 0xb7, (byte) 0x73, (byte) 0x30, (byte) 0x4e, (byte) 0xec };
// Initialisation
SecretKeySpec key = new SecretKeySpec(keyBytes, "AES");
IvParameterSpec ivSpec = new IvParameterSpec(ivBytes);
// Mode
Cipher cipher = Cipher.getInstance("AES/CTR/NoPadding");
String originalText = "hello i am original";
// ///////////////////////////////ENCRYPTING
cipher.init(Cipher.ENCRYPT_MODE, key, ivSpec);
byte[] ciphered = cipher.doFinal(originalText.getBytes());
String cipherText = new String(ciphered,"UTF-8");
System.out.println("ciphered: " + cipherText);
// ///////////////////////////////DECRYPTING
cipher = Cipher.getInstance("AES/CTR/NoPadding");
cipher.**init(Cipher.DECRYPT_MODE**, key, ivSpec);
byte[] plain = **cipher.doFinal(ciphered);**
originalText = new String(plain,"UTF-8");
System.out.println("plaintext: " + originalText);
}
I couldn't figure out what am I doing wrong.any help is deeply appreciated.
also is this proper way to encrypted some data this time I am trying to encrypt 4byte city pin code.
thank in advance
////
I made those changes n' it's working fine but what's the issue if I passes
cipherText.getByte() in cipher.init() function. Like
byte[] plain = cipher.doFinal(cipherText.getByte("UTF-8"));
n' Thanks for all your help.
For decryption you need to initialize the Cipher in DECRYPT_MODE. And also the byte[] to String conversion is not correct (See other answer).
You cannot convert the encrypted bytes to a String like that. "bytes" and "chars" are two entirely different things. remove the code which turns the bytes to a String and back again between encrypting and decrypting and your code should work (as pointed out in other answer, the second step should be using DECRYPT_MODE).
note that you need to be careful when using the platform character encoding to convert between bytes and chars/String, as this may be different on different platforms. this may cause problems if your data needs to move cross platform. it can also be lossy if your default platform encoding doesn't support all the characters in the text you are using.
Someone asked me to decrypt with PHP a string encrypted with the following Java Class.
public class CryptoLibrary {
private Cipher encryptCipher;
private sun.misc.BASE64Encoder encoder = new sun.misc.BASE64Encoder();
public CryptoLibrary() throws SecurityException{
java.security.Security.addProvider(new com.sun.crypto.provider.SunJCE());
char[] pass = "NNSHHETJKKSNKH".toCharArray();
byte[] salt = {
(byte) 0xa3, (byte) 0x21, (byte) 0x24, (byte) 0x2c,
(byte) 0xf2, (byte) 0xd2, (byte) 0x3e, (byte) 0x19 };
init(pass, salt, iterations);
}
public void init(char[] pass, byte[] salt, int iterations)throws SecurityException{
PBEParameterSpec ps = new javax.crypto.spec.PBEParameterSpec(salt, 20);
SecretKeyFactory kf = SecretKeyFactory.getInstance("PBEWithMD5AndDES");
SecretKey k = kf.generateSecret(new javax.crypto.spec.PBEKeySpec(pass));
encryptCipher = Cipher.getInstance("PBEWithMD5AndDES/CBC/PKCS5Padding");
encryptCipher.init(Cipher.ENCRYPT_MODE, k, ps);
}
}
public synchronized String encrypt(String str) throws SecurityException{
if(str!=null){
byte[] utf8 = str.getBytes("UTF8");
byte[] enc = encryptCipher.doFinal(utf8);
return encoder.encode(enc);
}
else {
return null;
}
}
}
I don't know any Java so I need some help to understand this encryption.
1) what is the meaning of this line?
PBEParameterSpec ps = new javax.crypto.spec.PBEParameterSpec(salt,20);
2) what value should I use for the first parameter of
string mcrypt_encrypt ( string $cipher , string $key , string $data , string $mode [, string $iv ] )
3) When should I use MD5 in my php script?
I had to do the same thing for a customer of mine and wrote a few lines of code to help with issue: https://github.com/kevinsandow/PBEWithMD5AndDES
1) It creates the parameters for Password Based Encryption, the salt, which is included in the hash calculations, and the number of iterations that the hash method is executed (on it's own output). It is used to defeat rainbow table attacks, basically an attacker has to go through the same number of iterations to check if the password is correct, and he cannot use a precalculated table because the salt will be different for each password (so you cannot see if somebody has the same password as another user).
2) MCRYPT_DES, and you will need MCRYPT_MODE_CBC for the mode, and PKCS#5 padding of course.
3) Only when you are absolutely sure that its weaknesses are not exposed or when absolutely required for compatibility. Fortunately, it is relatively secure for key derivation functions. Download a pbkdf1 method for PHP and put it in there - if not already included.