I am trying to upgrade my RSA key pair size from 1024 to 2048. I am going to give the sample code here.
For 1024:
The first step in my code is to build a signature using Java code
try {
// Generate Public & Private Keys
KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA");
kpg.initialize(1024);
KeyPair keyPair = kpg.genKeyPair();
RSAPublicKey key = (RSAPublicKey) keyPair.getPublic();
System.out.println("Modulus");
System.out.println(key.getModulus());
String publicKey = encode(key.getModulus().toByteArray()) + encode(key.getPublicExponent().toByteArray());
// Build the Signature
Signature sig = Signature.getInstance("SHA256withRSA");
sig.initSign(keyPair.getPrivate());
seedData = seedData + "ABC";
sig.update(seedData.getBytes("UTF-8"));
String signature = encode(sig.sign());
signatureString.append(signature);
// Embed the public key in the signature
int publicKeyLocation = signatureString.charAt(signatureString.length() - 2) % 40;
// Add some random bytes at the end
int randomBytesToAdd = signatureString.charAt(signatureString.length() - 10) % 9;
System.out.println("Random bytes to add");
System.out.println(randomBytesToAdd);
System.out.println("Adding Random bytes that are:");
String s = randomString(randomBytesToAdd);
signatureString.insert(publicKeyLocation, s);
} catch (Exception e) {
}
return signatureString.toString();
}
Now this encoded string in passed to .NET application
Where in the .NET application i used to get public key and signature
protected RSAParameters GetPublicKey(string encodedString)
{
int bytesToSkip = encodedString.Substring((encodedString.Length - 10), 1).ToCharArray()[0] % 9;
int publicKeyStarts = bytesToSkip + encodedString.Substring((encodedString.Length - 2), 1).ToCharArray()[0] % 40;
string keyString = encodedString.Substring(publicKeyStarts, 176);
string modulusString = keyString.Substring(0, 172);
string exponentString = keyString.Substring(172);
RSAParameters publicKey = new RSAParameters();
publicKey.Modulus = new byte[128];
Array.Copy(System.Convert.FromBase64String(modulusString), 1, publicKey.Modulus, 0, 128);
publicKey.Exponent = System.Convert.FromBase64String(exponentString);
return publicKey;
}
protected byte[] GetSignature(string encodedString)
{
int bytesToSkip = encodedString.Substring((encodedString.Length - 10), 1).ToCharArray()[0] % 9;
int publicKeyStarts = bytesToSkip + encodedString.Substring((encodedString.Length - 2), 1).ToCharArray()[0] % 40;
String digSig = encodedString.Substring(0, publicKeyStarts - bytesToSkip) + encodedString.Substring(publicKeyStarts + 176);
return System.Convert.FromBase64String(digSig);
}
If I change the key pair size from 1024 to 2048 what are the changes i need to make in my .NET code
The answer I am looking for is: from the generated RSA public key how can I get length of Modulus and Exponent
I found the answer to get the size or length of modulus and exponent from the RSA public key
code to generate public key
KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA");
kpg.initialize(2048);
KeyPair keyPair = kpg.genKeyPair();
RSAPublicKey key = (RSAPublicKey) keyPair.getPublic();
String publicKey = encode(key.getModulus().toByteArray()) + encode(key.getPublicExponent().toByteArray());
int modLength = key.getModulus().toByteArray().length;
int exponentLength = key.getPublicExponent().toByteArray().length;
Related
While performing Elliptic Curve cryptography with secp256k1 curve, I noticed that while the code and test cases compile on the Android Studio IDE they do not compile on the android device since the curve isn't defined in the jre/jdk that the mobile device uses. Changing the curve to a prime256v1 I seem to be facing difficulties in converting a hex string of the publicKey to PublicKey object.
Given the hex string of a PublicKey.getEncoded() which is in a database. I want an android client to convert the byte[] from converting the hex string into a PublicKey object. I am converting the byte[] using X509EncodedKeySpec() as follows:
public static PublicKey getPublicKey(byte[] pk) throws NoSuchAlgorithmException, InvalidKeySpecException {
EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(pk);
KeyFactory kf = KeyFactory.getInstance("EC");
PublicKey pub = kf.generatePublic(publicKeySpec);
return pub;
}
The conversion from a hex string to a byte[] happens as follows:
public static byte[] hexStringToByteArray(String hexString) {
byte[] bytes = new byte[hexString.length() / 2];
for(int i = 0; i < hexString.length(); i += 2){
String sub = hexString.substring(i, i + 2);
Integer intVal = Integer.parseInt(sub, 16);
bytes[i / 2] = intVal.byteValue();
String hex = "".format("0x%x", bytes[i / 2]);
}
return bytes;
}
The conversion from a byte[] to Hex string is as follows:
public static String convertBytesToHex(byte[] bytes) {
char[] hexChars = new char[bytes.length * 2];
for ( int j = 0; j < bytes.length; j++ ) {
int v = bytes[j] & 0xFF;
hexChars[j * 2] = hexArray[v >>> 4];
hexChars[j * 2 + 1] = hexArray[v & 0x0F];
}
return new String(hexChars).toLowerCase();
}
However when I run this on the android app (7.0, API 24) I get the following System Error
W/System.err: java.security.spec.InvalidKeySpecException: java.lang.RuntimeException: error:0c0000b9:ASN.1 encoding routines:OPENSSL_internal:WRONG_TAG
at com.android.org.conscrypt.OpenSSLKey.getPublicKey(OpenSSLKey.java:295)
at com.android.org.conscrypt.OpenSSLECKeyFactory.engineGeneratePublic(OpenSSLECKeyFactory.java:47)
at java.security.KeyFactory.generatePublic(KeyFactory.java:357)
What is the recommended approach for converting a Hex string to a PublicKey for EC instance on an android device.
Here's sample code that you can execute:
ECDSA.java
public class ECDSA {
public static KeyPair generateKeyPair() throws NoSuchAlgorithmException, InvalidAlgorithmParameterException {
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("EC");
ECGenParameterSpec ecSpec = new ECGenParameterSpec("prime256v1");
SecureRandom random = SecureRandom.getInstance("SHA1PRNG");
keyGen.initialize(ecSpec, random);
KeyPair pair = keyGen.generateKeyPair();
return pair;
}
public static PublicKey getPublicKey(byte[] pk) throws NoSuchAlgorithmException, InvalidKeySpecException {
EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(pk);
KeyFactory kf = KeyFactory.getInstance("EC");
PublicKey pub = kf.generatePublic(publicKeySpec);
return pub;
}
public static PrivateKey getPrivateKey(byte[] privk) throws NoSuchAlgorithmException, InvalidKeySpecException {
EncodedKeySpec privateKeySpec = new X509EncodedKeySpec(privk);
KeyFactory kf = KeyFactory.getInstance("EC");
PrivateKey privateKey = kf.generatePrivate(privateKeySpec);
return privateKey;
}
}
MainActivity.java
public class MainActivity extends AppCompatActivity {
KeyPair keyPair = ECDSA.generateKeyPair();
PublicKey publicKey = keyPair.getPublic();
PrivateKey privateKey = keyPair.getPrivate();
// Converting byte[] to Hex
publicKeyHex = convertBytesToHex(publicKey.getEncoded());
privateKeyHex = convertBytesToHex(privateKey.getEncoded());
// Trying to convert Hex to PublicKey/PrivateKey objects
PublicKey pkReconstructed = ECDSA.getPublicKey(hexStringToByteArray(publicKeyHex));
PrivateKey skReconstructed = ECDSA.getPrivateKey(hexStringToByteArray(privateKeyHex));
// This throws an error when running on an android device
// because there seems to be some library mismatch with
// java.security.* vs conscrypt.OpenSSL.* on android.
}
Finally we get a real MCVE and we can now see that you are not using the correct class for encoded private keys. X509EncodedKeySpec is only for public keys. From the javadocs (emphasis mine):
This class represents the ASN.1 encoding of a public key, encoded
according to the ASN.1 type SubjectPublicKeyInfo.
For private keys, the correct encoding is usually a PKCS8EncodedKeySpec. The encoding can be determined by examining the output of Key.getFormat(). Therefore, change your method getPrivateKey of ECDSA to
public static PrivateKey getPrivateKey(byte[] privk) throws NoSuchAlgorithmException, InvalidKeySpecException {
EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(privk);
KeyFactory kf = KeyFactory.getInstance("EC");
PrivateKey privateKey = kf.generatePrivate(privateKeySpec);
return privateKey;
}
I am trying to learn some Go and blockchains.. Starting with ECDSA signatures. Trying to figure out how to test if I had a correctly working Go implementation of ECDSA signatures, I figured I would try to create a similar version in Java and compare the results to see if I can get them to match.
So Java attempt:
public static void main(String[] args) throws Exception {
//the keys below are previously generated with "generateKey();" and base64 encoded
generateKey();
String privStr = "MEECAQAwEwYHKoZIzj0CAQYIKoZIzj0DAQcEJzAlAgEBBCAQ7bMVIcWr9NpSD3hPkns5C0qET87UvyY5WI6UML2p0Q==";
String pubStr = "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAES8VdACZT/9u1NmaiQk0KIjEXxiaxms74nu/ps6bP0OvYMIlTdIWWU2s35LEKsNJH9u5QM2ocX53BPjwbsENXJw==";
PrivateKey privateKey = base64ToPrivateKey(privStr);
PublicKey publicKey = base64ToPublicKey(pubStr);
String str = "This is string to sign";
byte[] signature = signMsg(str, privateKey);
boolean ok = verifySignature(publicKey, str, signature);
System.out.println("signature ok:" + ok);
String privHex = getPrivateKeyAsHex(privateKey);
}
public static byte[] signMsg(String msg, PrivateKey priv) throws Exception {
Signature ecdsa = Signature.getInstance("SHA1withECDSA");
ecdsa.initSign(priv);
byte[] strByte = msg.getBytes("UTF-8");
ecdsa.update(strByte);
byte[] realSig = ecdsa.sign();
//the printed signature from here is what is used in the Go version (hex string)
System.out.println("Signature: " + new BigInteger(1, realSig).toString(16));
return realSig;
}
//https://stackoverflow.com/questions/30175149/error-when-verifying-ecdsa-signature-in-java-with-bouncycastle
private static boolean verifySignature(PublicKey pubKey, String msg, byte[] signature) throws Exception {
byte[] message = msg.getBytes("UTF-8");
Signature ecdsa = Signature.getInstance("SHA1withECDSA");
ecdsa.initVerify(pubKey);
ecdsa.update(message);
return ecdsa.verify(signature);
}
public static String generateKey() throws Exception {
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("EC");
SecureRandom random = SecureRandom.getInstance("SHA1PRNG");
keyGen.initialize(256, random); //256 bit key size
KeyPair pair = keyGen.generateKeyPair();
PrivateKey priv = pair.getPrivate();
ECPrivateKey ePriv = (ECPrivateKey) priv;
PublicKey pub = pair.getPublic();
//https://stackoverflow.com/questions/5355466/converting-secret-key-into-a-string-and-vice-versa
String encodedPrivateKey = Base64.getEncoder().encodeToString(priv.getEncoded());
byte[] pubEncoded = pub.getEncoded();
String encodedPublicKey = Base64.getEncoder().encodeToString(pubEncoded);
System.out.println(encodedPrivateKey);
System.out.println(encodedPublicKey);
return encodedPrivateKey;
}
public static PrivateKey base64ToPrivateKey(String encodedKey) throws Exception {
byte[] decodedKey = Base64.getDecoder().decode(encodedKey);
return bytesToPrivateKey(decodedKey);
}
public static PrivateKey bytesToPrivateKey(byte[] pkcs8key) throws GeneralSecurityException {
PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(pkcs8key);
KeyFactory factory = KeyFactory.getInstance("EC");
PrivateKey privateKey = factory.generatePrivate(spec);
return privateKey;
}
public static PublicKey base64ToPublicKey(String encodedKey) throws Exception {
byte[] decodedKey = Base64.getDecoder().decode(encodedKey);
return bytesToPublicKey(decodedKey);
}
public static PublicKey bytesToPublicKey(byte[] x509key) throws GeneralSecurityException {
X509EncodedKeySpec spec = new X509EncodedKeySpec(x509key);
KeyFactory factory = KeyFactory.getInstance("EC");
PublicKey publicKey = factory.generatePublic(spec);
return publicKey;
}
//https://stackoverflow.com/questions/40552688/generating-a-ecdsa-private-key-in-bouncy-castle-returns-a-public-key
private static String getPrivateKeyAsHex(PrivateKey privateKey) {
ECPrivateKey ecPrivateKey = (ECPrivateKey) privateKey;
byte[] privateKeyBytes = ecPrivateKey.getS().toByteArray();
System.out.println("S:"+ecPrivateKey.getS());
String hex = bytesToHex(privateKeyBytes);
System.out.println("Private key bytes: " + Arrays.toString(privateKeyBytes));
System.out.println("Private key hex: " + hex);
return hex;
}
private final static char[] hexArray = "0123456789ABCDEF".toCharArray();
public static String bytesToHex(byte[] bytes) {
char[] hexChars = new char[bytes.length * 2];
for (int j = 0 ; j < bytes.length ; j++) {
int v = bytes[j] & 0xFF;
hexChars[j * 2] = hexArray[v >>> 4];
hexChars[j * 2 + 1] = hexArray[v & 0x0F];
}
return new String(hexChars);
}
Doing the signing and verification in Java works just fine. Easy to configure of course, since they are all the same libs, parameters, and all.
To verify the same signature in Go, I tried:
func TestSigning(t *testing.T) {
privKey := hexToPrivateKey("10EDB31521C5ABF4DA520F784F927B390B4A844FCED4BF2639588E9430BDA9D1")
pubKey := privKey.Public()
sig := "3045022071f06054f450f808aa53294d34f76afd288a23749628cc58add828e8b8f2b742022100f82dcb51cc63b29f4f8b0b838c6546be228ba11a7c23dc102c6d9dcba11a8ff2"
sigHex, _ := hex.DecodeString(sig)
ePubKey := pubKey.(*ecdsa.PublicKey)
ok := verifyMySig(ePubKey, "This is string to sign", sigHex)
println(ok)
}
func verifyMySig(pub *ecdsa.PublicKey, msg string, sig []byte) bool {
r := new(big.Int).SetBytes(sig[:len(sig)/2])
s := new(big.Int).SetBytes(sig[len(sig)/2:])
return ecdsa.Verify(pub, []byte(msg), r, s)
}
func hexToPrivateKey(hexStr string) *ecdsa.PrivateKey {
bytes, _ := hex.DecodeString(hexStr)
k := new(big.Int)
k.SetBytes(bytes)
println("k:")
fmt.Println(k.String())
priv := new(ecdsa.PrivateKey)
curve := elliptic.P256()
priv.PublicKey.Curve = curve
priv.D = k
priv.PublicKey.X, priv.PublicKey.Y = curve.ScalarBaseMult(k.Bytes())
return priv
}
Initially, I tried to just export the Private key in Java as a base64 encoded string, and import that into Go. But I could not figure out how to get Go to load the key in the format Java stores if (X509EncodedKeySpec). So instead, I tried this way to copy the big integer of the private key only, and generate the public key from that. If I get that to work, then try to copy just the public key..
Anyway, the Go code fails to verify the signature. It is always false. Also, I cannot figure out where to put the SHA function in Go from "SHA1withECDSA" part.
I am sure I am missing some basic concepts here. How to do this properly?
Managed to get this to work. So just to document it for myself and anyone interested..
As pointed by in comments, the signature from Java is in ASN1 format. Found a nice description of the format here: https://crypto.stackexchange.com/questions/1795/how-can-i-convert-a-der-ecdsa-signature-to-asn-1.
I also found some good examples on how to do SHAxx with ECDSA in Go at https://github.com/gtank/cryptopasta (sign.go and sign_test.go). Just need to run the relevant SHA function before the ECDSA code.
Found example code for building the public keys from parameters in Go at http://codrspace.com/supcik/golang-jwt-ecdsa/.
I paste the relevant code below, if someone finds an issue, please let me know..
Relevant Java code:
public static PublicKey bytesToPublicKey(byte[] x509key) throws GeneralSecurityException {
X509EncodedKeySpec spec = new X509EncodedKeySpec(x509key);
KeyFactory factory = KeyFactory.getInstance("EC");
ECPublicKey publicKey = (ECPublicKey) factory.generatePublic(spec);
//We should be able to use these X and Y in Go to build the public key
BigInteger x = publicKey.getW().getAffineX();
BigInteger y = publicKey.getW().getAffineY();
System.out.println(publicKey.toString());
return publicKey;
}
//we can either use the Java standard signature ANS1 format output, or just take the R and S parameters from it, and pass those to Go
//https://stackoverflow.com/questions/48783809/ecdsa-sign-with-bouncycastle-and-verify-with-crypto
public static BigInteger extractR(byte[] signature) throws Exception {
int startR = (signature[1] & 0x80) != 0 ? 3 : 2;
int lengthR = signature[startR + 1];
return new BigInteger(Arrays.copyOfRange(signature, startR + 2, startR + 2 + lengthR));
}
public static BigInteger extractS(byte[] signature) throws Exception {
int startR = (signature[1] & 0x80) != 0 ? 3 : 2;
int lengthR = signature[startR + 1];
int startS = startR + 2 + lengthR;
int lengthS = signature[startS + 1];
return new BigInteger(Arrays.copyOfRange(signature, startS + 2, startS + 2 + lengthS));
}
public static byte[] signMsg(String msg, PrivateKey priv) throws Exception {
Signature ecdsa = Signature.getInstance("SHA1withECDSA");
ecdsa.initSign(priv);
byte[] strByte = msg.getBytes("UTF-8");
ecdsa.update(strByte);
byte[] realSig = ecdsa.sign();
//this is the R and S we could also pass as the signature
System.out.println("R: "+extractR(realSig));
System.out.println("S: "+extractS(realSig));
return realSig;
}
Relevant Go code:
func verifyMySig(pub *ecdsa.PublicKey, msg string, sig []byte) bool {
//https://github.com/gtank/cryptopasta
digest := sha1.Sum([]byte(msg))
var esig ecdsaSignature
asn1.Unmarshal(sig, &esig)
//above is ASN1 decoding from the Java format. Alternatively, we can just transfer R and S parameters and set those
// esig.R.SetString("89498588918986623250776516710529930937349633484023489594523498325650057801271", 0)
// esig.S.SetString("67852785826834317523806560409094108489491289922250506276160316152060290646810", 0)
fmt.Printf("R: %d , S: %d", esig.R, esig.S)
println()
return ecdsa.Verify(pub, digest[:], esig.R, esig.S)
}
func hexToPrivateKey(hexStr string) *ecdsa.PrivateKey {
bytes, err := hex.DecodeString(hexStr)
print(err)
k := new(big.Int)
k.SetBytes(bytes)
println("k:")
fmt.Println(k.String())
priv := new(ecdsa.PrivateKey)
curve := elliptic.P256()
priv.PublicKey.Curve = curve
priv.D = k
priv.PublicKey.X, priv.PublicKey.Y = curve.ScalarBaseMult(k.Bytes())
//we can check these against the Java implementation to see if it matches to know key was transferred OK
fmt.Printf("X: %d, Y: %d", priv.PublicKey.X, priv.PublicKey.Y)
println()
return priv
}
I am attempting to implement this encryption scheme with AES encryption.
Basically it goes as follows:
user data is encrypted with a surrogate key (surrogateKey)
the surrogate key is XORed with a key derived from the password
(passwordKey) and stored (storedKey)
when the key is needed (to encrypt or decrypt user data) the storedKey is retrieved from the DB and XORed again with the freshly generated passwordKey to recover the surrogateKey
Except, I must be doing something wrong in implementation, because I can never seem to recover a valid surrogateKey and any attempt at decryption is giving me a BadPaddingException.
The code below demonstrates the issue. Sorry if it's a bit long, but you should be able to just copy and paste it into your IDE.`
import java.io.ByteArrayOutputStream;
import java.security.AlgorithmParameters;
import java.security.SecureRandom;
import java.security.spec.KeySpec;
import java.util.Arrays;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
import javax.xml.bind.DatatypeConverter;
public class SurrogateTest {
private static final String alphanumeric =
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ "abcdefghijklmnopqrstuvwxyz"
+ "1234567890"
+ "!#$%&()+*<>?_-=^~|";
private static String plainText = "I am the very model of a modern major general";
private static String cipherText = "";
private static SecureRandom rnd = new SecureRandom();
private static byte[] salt;
public static void main(String[] args) {
// arguments are password and recovery string
if (args.length > 1) {
System.out.println("password: " + args[0] + "; recovery: " + args[1]);
}
String password = args[0];
// passwordKey
SecretKey passwordKey = getKey(password);
System.out.println("passwordKey: " + DatatypeConverter.printBase64Binary(passwordKey.getEncoded()));
// Generate surrogate encryption key from random string
String rand = randomString(24);
SecretKey surrogateKey = getKey(rand);
byte[] surrogateByteArray = surrogateKey.getEncoded();
System.out.println("surrogate: " + DatatypeConverter.printBase64Binary(surrogateByteArray));
// encrypt plainText
System.out.println("text to encrypt: " + plainText);
cipherText = encryptWithKey(plainText, surrogateKey);
// XOR surrogateKey with passwordKey to get storedKey
SecretKey storedKey = xorWithKey(surrogateKey, passwordKey);
String storedKeyString = DatatypeConverter.printBase64Binary(storedKey.getEncoded());
System.out.println("storedKey: " + storedKeyString);
byte[] storedKey2Array = DatatypeConverter.parseBase64Binary(storedKeyString);
SecretKey storedKey2 = new SecretKeySpec(storedKey2Array, 0, storedKey2Array.length, "AES");
String storedKey2String = DatatypeConverter.printBase64Binary(storedKey2.getEncoded());
System.out.println("storedKey->String->key->string: " + storedKey2String);
// recover surrogateKey from storedKey2
SecretKey password2Key = getKey(password);
System.out.println("password2Key: " + DatatypeConverter.printBase64Binary(password2Key.getEncoded()));
SecretKey surrogate2Key = xorWithKey(storedKey2, password2Key);
System.out.println("surrogate2 (recovered): " + DatatypeConverter.printBase64Binary(surrogate2Key.getEncoded()));
// decrypt text
String decryptedText = decryptWithKey(cipherText, surrogate2Key);
System.out.println("decryptedText: " + decryptedText);
}
private static SecretKey xorWithKey(SecretKey a, SecretKey b) {
byte[] out = new byte[b.getEncoded().length];
for (int i = 0; i < b.getEncoded().length; i++) {
out[i] = (byte) (b.getEncoded()[i] ^ a.getEncoded()[i % a.getEncoded().length]);
}
SecretKey outKey = new SecretKeySpec(out, 0, out.length, "AES");
return outKey;
}
private static String randomString(int length) {
StringBuilder sb = new StringBuilder(length);
for (int i = 0; i < length; i++)
sb.append(alphanumeric.charAt(rnd.nextInt(alphanumeric.length())));
return sb.toString();
}
// return encryption key
private static SecretKey getKey(String password) {
try {
SecureRandom random = new SecureRandom();
salt = new byte[16];
random.nextBytes(salt);
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
// obtain secret key
KeySpec spec = new PBEKeySpec(password.toCharArray(), salt, 65536, 256);
SecretKey tmp = factory.generateSecret(spec);
SecretKey secret = new SecretKeySpec(tmp.getEncoded(), "AES");
return secret;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
private static String encryptWithKey(String str, SecretKey secret) {
try {
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, secret);
AlgorithmParameters params = cipher.getParameters();
byte[] iv = params.getParameterSpec(IvParameterSpec.class).getIV();
byte[] encryptedText = cipher.doFinal(str.getBytes("UTF-8")); // encrypt the message str here
// concatenate salt + iv + ciphertext
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
outputStream.write(salt);
outputStream.write(iv);
outputStream.write(encryptedText);
// properly encode the complete ciphertext
String encrypted = DatatypeConverter.printBase64Binary(outputStream.toByteArray());
return encrypted;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
private static String decryptWithKey(String str, SecretKey secret) {
try {
byte[] ciphertext = DatatypeConverter.parseBase64Binary(str);
if (ciphertext.length < 48) {
return null;
}
salt = Arrays.copyOfRange(ciphertext, 0, 16);
byte[] iv = Arrays.copyOfRange(ciphertext, 16, 32);
byte[] ct = Arrays.copyOfRange(ciphertext, 32, ciphertext.length);
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, secret, new IvParameterSpec(iv));
byte[] plaintext = cipher.doFinal(ct);
return new String(plaintext, "UTF-8");
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
Any ideas what I'm doing wrong?
Running the posted code shows that surrogate2 (recovered) is different from surrogate. It should be obvious this is badly wrong. The reason is that at encryption you derive the 'password' (wrapping) key using a random salt and write that salt at the beginning of your data blob; at decryption you derive the unwrapping key using a new salt and then read the correct salt from the blob and totally ignore it. This means your unwrapping key is wrong, so your unwrapped data key is wrong, so your decryption is wrong.
PS: the random key used to directly encrypt and decrypt the data is usually called a 'data' key (as I just did) or DEK (abbreviation for Data Encryption or Encrypting Key), or a more specific term like 'session key' or 'message key' (in this case), or 'working key' or 'transient key' to emphasize its limited scope. It is not usually called 'surrogate'. And using PBKDF2 to derive this data key from a strongly random string is a waste of time; just use SecureRandom_instance.nextBytes(byte[]) directly for the data key.
I’m trying to use web push notifications with the web push protocol in my app. In order to use the Push API with VAPID I need an applicationServerKey.
The PushManager subscribe method takes a VAPID key (public key alone) as a parameter and will give a subscription end point and keys to push messages.
To generate VAPID keys, I have been using node.js (google web-push package) and openssl till now. But in my use case VAPID keys should be generated within Java and passed to JavaScript to subscribe from the browser.
I am trying with the code below in Java to generate VAPID keys. I am able to create keys successfully but when I pass the generated public key (base64-encoded string), the subscribe method returns an error saying:
Unable to register service worker. DOMException: Failed to execute
'subscribe' on 'PushManager': The provided applicationServerKey is not
valid..
Please help me resolve this issue. Below is my Java code:
ECNamedCurveParameterSpec parameterSpec =
ECNamedCurveTable.getParameterSpec("prime256v1");
KeyPairGenerator keyPairGenerator =
KeyPairGenerator.getInstance("ECDH", "BC");
keyPairGenerator.initialize(parameterSpec);
KeyPair serverKey = keyPairGenerator.generateKeyPair();
PrivateKey priv = serverKey.getPrivate();
PublicKey pub = serverKey.getPublic();`
System.out.println(Base64.toBase64String(pub.getEncoded()));
Please refer below link for answer from MartijnDwars.
https://github.com/web-push-libs/webpush-java/issues/30
you can use Utils.savePublicKey to convert your Java-generated
PublicKey to a byte[]. This byte[] is then passed to the
PushManager.subscribe method.
It may be more convenient to base64 encode the byte[] in Java and
base64 decode the string in JavaScript. For example, after generating
the keypair in Java:
KeyPair keyPair = generateKeyPair();
byte[] publicKey = Utils.savePublicKey((ECPublicKey) keyPair.getPublic());
String publicKeyBase64 = BaseEncoding.base64Url().encode(publicKey);
System.out.println("PublicKey = " + publicKeyBase64);
// PublicKey = BPf36QAqZNNvvnl9kkpTDerXUOt6Nm6P4x9GEvmFVFKgVyCVWy24KUTs6wLQtbV2Ug81utbNnx86_vZzXDyrl88=
Then in JavaScript:
function subscribe() {
const publicKey = base64UrlToUint8Array('BPf36QAqZNNvvnl9kkpTDerXUOt6Nm6P4x9GEvmFVFKgVyCVWy24KUTs6wLQtbV2Ug81utbNnx86_vZzXDyrl88=');
navigator.serviceWorker.ready.then(function (serviceWorkerRegistration) {
serviceWorkerRegistration.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: publicKey
})
.then(function (subscription) {
return sendSubscriptionToServer(subscription);
})
.catch(function (e) {
if (Notification.permission === 'denied') {
console.warn('Permission for Notifications was denied');
} else {
console.error('Unable to subscribe to push.', e);
}
});
});
}
function base64UrlToUint8Array(base64UrlData) {
const padding = '='.repeat((4 - base64UrlData.length % 4) % 4);
const base64 = (base64UrlData + padding)
.replace(/\-/g, '+')
.replace(/_/g, '/');
const rawData = atob(base64);
const buffer = new Uint8Array(rawData.length);
for (let i = 0; i < rawData.length; ++i) {
buffer[i] = rawData.charCodeAt(i);
}
return buffer;
}
Having spent hours on this, I thought I would share the solution I worked out from gleaning several web sites, avoiding using Bouncy Castle which generated lots of other issues. Try the below:
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("EC");
ECGenParameterSpec spec = new ECGenParameterSpec("secp256r1");
keyPairGenerator.initialize(spec, new SecureRandom());
KeyPair keyPair = keyPairGenerator.generateKeyPair();
ECPublicKey publicKey = (ECPublicKey) keyPair.getPublic();
ECPoint ecp = publicKey.getW();
byte[] x = ecp.getAffineX().toByteArray();
byte[] y = ecp.getAffineY().toByteArray();
// Convert 04 to bytes
String s= "04";
int len = s.length();
byte[] firstBit = new byte[len / 2];
for (int i = 0; i < len; i += 2)
{
firstBit[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) +
Character.digit(s.charAt(i+1), 16));
}
ByteArrayOutputStream outputStream = new ByteArrayOutputStream( );
outputStream.write(firstBit);
outputStream.write(x);
outputStream.write(y);
publicKeyBytes = outputStream.toByteArray( );
Base64 encoder = new Base64(-1,null,true);
byte[] encodedBytes = encoder.encode(publicKeyBytes);
String publicKeyBase64 = new String(encodedBytes, StandardCharsets.UTF_8);
With bountycastle you can generate vapid keys with this code :
ECNamedCurveParameterSpec parameterSpec = ECNamedCurveTable.getParameterSpec("prime256v1");
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("ECDH", "BC");
keyPairGenerator.initialize(parameterSpec);
KeyPair keyPair = keyPairGenerator.generateKeyPair();
ECPublicKey publicKey = (ECPublicKey) keyPair.getPublic();
ECPrivateKey privateKey = (ECPrivateKey) keyPair.getPrivate();
String publicKeyString = Base64.getUrlEncoder().withoutPadding().encodeToString(publicKey.getQ().getEncoded(false));
System.out.println(publicKeyString);
String privateKeyString = Base64.getUrlEncoder().withoutPadding().encodeToString(privateKey.getD().toByteArray());
System.out.println(privateKeyString);
This is a solution with poor Java 8 that handle the random length of the BigIntegers.
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance( "EC" );
keyPairGenerator.initialize( new ECGenParameterSpec( "secp256r1" ), new SecureRandom() );
KeyPair keyPair = keyPairGenerator.generateKeyPair();
ECPublicKey publicKey = (ECPublicKey)keyPair.getPublic();
ECPoint ecp = publicKey.getW();
byte[] applicationServerKey = new byte[65];
applicationServerKey[0] = 4;
// copy getAffineX() to the target
byte[] affine = ecp.getAffineX().toByteArray(); // typical 31 to 33 bytes
int pos = 1;
int off, length;
off = affine.length - 32;
if( off >= 0 ) {
// there are leading zero values which we cut
length = 32;
} else {
pos -= off;
length = 32 + off;
off = 0;
}
System.arraycopy( affine, off, applicationServerKey, pos, length );
// copy getAffineY() to the target
affine = ecp.getAffineY().toByteArray(); // typical 31 to 33 bytes
pos = 33;
off = affine.length - 32;
if( off >= 0 ) {
// there are leading zero values which we cut
length = 32;
} else {
pos -= off;
length = 32 + off;
off = 0;
}
System.arraycopy( affine, off, applicationServerKey, pos, length );
return Base64.getEncoder().encodeToString( applicationServerKey );
or more simpler with the Java buildin encoding:
ECPublicKey publicKey = ...
byte[] keyBytes = publicKey.getEncoded();
// 26 -> X509 overhead, length ever 91, results in 65 bytes
keyBytes = Arrays.copyOfRange( keyBytes, 26, 91 );
return this.applicationServerKey = Base64.getEncoder().encodeToString( keyBytes );
I'm trying to support PBE for AES, Serpent, and TwoFish. Currently I am able to generate an AES PBEKey in Java using BC like this:
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBEWITHSHA256AND256BITAES-CBC-BC", provider);
PBEKeySpec pbeKeySpec = new PBEKeySpec("Password12".toCharArray());
SecretKey key = factory.generateSecret(pbeKeySpec);
but I can't figure out how to generate a PBEKey for Serpent, so I'm assuming its not possible out of the box. How would I go about implementing this? Is there a hook somewhere that I can just register my own SecretKeyFactory to handle Serpent keys?
Coincidentally, I have noticed that using an AES PBEKey (as generated above) for encrypting/decrypting with Serpent/TwoFish "works", but I have no idea what the repercussions are. Could I just get away with using the AES PBEKey?
After discussions with PaŭloEbermann (above), I put together the following solution. It generates a PBE key for AES256 and then simply copies the required number of bytes from the generated key into a new SecretKeySpec(), which allows me to specify the desired algorithm and key length. Currently I am salting the password AND creating a random IV on each call to encrypt. My assumption is that the IV is unnecessary since a random salt is applied to each encrypted message, but I wasn't 100% sure so I added the IV anyway. I'm hoping someone can confirm or deny this assumption, since if the IV isnt needed, then its bloating the size of the output from encrypt() for no valid reason. Ideally I would be able to generate a PBEKey of variable length with no algorithm ties (as per PKCS5), but it appears I am bound to the key sizes defined in the available PBE schemes provided by the selected Provider. This implementation is therefore bound to using BouncyCastle, since I was unable to find a PBE scheme that provided at least 256bit keys from the standard JCE provider.
/**
* parts of this code were copied from the StandardPBEByteEncryptor class from the Jasypt (www.jasypt.org) project
*/
public class PBESample {
private final String KEY_ALGORITHM = "PBEWithSHA256And256BitAES-CBC-BC";
private final String MODE_PADDING = "/CBC/PKCS5Padding";
private final int DEFAULT_SALT_SIZE_BYTES = 16;
private final SecureRandom rand;
private final String passwd = "(Password){12}<.....>!";
public PBESample() throws Exception {
rand = SecureRandom.getInstance("SHA1PRNG");
}
private byte[] generateSalt(int size) {
byte[] salt = new byte[size];
rand.nextBytes(salt);
return salt;
}
private SecretKey generateKey(String algorithm, int keySize, byte[] salt) throws NoSuchProviderException, NoSuchAlgorithmException, InvalidKeySpecException{
SecretKeyFactory factory = SecretKeyFactory.getInstance(KEY_ALGORITHM);
PBEKeySpec pbeKeySpec = new PBEKeySpec(passwd.toCharArray(), salt, 100000);
SecretKey tmpKey = factory.generateSecret(pbeKeySpec);
byte[] keyBytes = new byte[keySize / 8];
System.arraycopy(tmpKey.getEncoded(), 0, keyBytes, 0, keyBytes.length);
return new SecretKeySpec(keyBytes, algorithm);
}
private byte[] generateIV(Cipher cipher) {
byte[] iv = new byte[cipher.getBlockSize()];
rand.nextBytes(iv);
return iv;
}
private byte[] appendArrays(byte[] firstArray, byte[] secondArray) {
final byte[] result = new byte[firstArray.length + secondArray.length];
System.arraycopy(firstArray, 0, result, 0, firstArray.length);
System.arraycopy(secondArray, 0, result, firstArray.length, secondArray.length);
return result;
}
public byte[] encrypt(String algorithm, int keySize, final byte[] message) throws Exception {
Cipher cipher = Cipher.getInstance(algorithm + MODE_PADDING);
// The salt size for the chosen algorithm is set to be equal
// to the algorithm's block size (if it is a block algorithm).
int saltSizeBytes = DEFAULT_SALT_SIZE_BYTES;
int algorithmBlockSize = cipher.getBlockSize();
if (algorithmBlockSize > 0) {
saltSizeBytes = algorithmBlockSize;
}
// Create salt
final byte[] salt = generateSalt(saltSizeBytes);
SecretKey key = generateKey(algorithm, keySize, salt);
// create a new IV for each encryption
final IvParameterSpec ivParamSpec = new IvParameterSpec(generateIV(cipher));
// Perform encryption using the Cipher
cipher.init(Cipher.ENCRYPT_MODE, key, ivParamSpec);
byte[] encryptedMessage = cipher.doFinal(message);
// append the IV and salt
encryptedMessage = appendArrays(ivParamSpec.getIV(), encryptedMessage);
encryptedMessage = appendArrays(salt, encryptedMessage);
return encryptedMessage;
}
public byte[] decrypt(String algorithm, int keySize, final byte[] encryptedMessage) throws Exception {
Cipher cipher = Cipher.getInstance(algorithm + MODE_PADDING);
// determine the salt size for the first layer of encryption
int saltSizeBytes = DEFAULT_SALT_SIZE_BYTES;
int algorithmBlockSize = cipher.getBlockSize();
if (algorithmBlockSize > 0) {
saltSizeBytes = algorithmBlockSize;
}
byte[] decryptedMessage = new byte[encryptedMessage.length];
System.arraycopy(encryptedMessage, 0, decryptedMessage, 0, encryptedMessage.length);
// extract the salt and IV from the incoming message
byte[] salt = null;
byte[] iv = null;
byte[] encryptedMessageKernel = null;
final int saltStart = 0;
final int saltSize = (saltSizeBytes < decryptedMessage.length ? saltSizeBytes : decryptedMessage.length);
final int ivStart = (saltSizeBytes < decryptedMessage.length ? saltSizeBytes : decryptedMessage.length);
final int ivSize = cipher.getBlockSize();
final int encMesKernelStart = (saltSizeBytes + ivSize < decryptedMessage.length ? saltSizeBytes + ivSize : decryptedMessage.length);
final int encMesKernelSize = (saltSizeBytes + ivSize < decryptedMessage.length ? (decryptedMessage.length - saltSizeBytes - ivSize) : 0);
salt = new byte[saltSize];
iv = new byte[ivSize];
encryptedMessageKernel = new byte[encMesKernelSize];
System.arraycopy(decryptedMessage, saltStart, salt, 0, saltSize);
System.arraycopy(decryptedMessage, ivStart, iv, 0, ivSize);
System.arraycopy(decryptedMessage, encMesKernelStart, encryptedMessageKernel, 0, encMesKernelSize);
SecretKey key = generateKey(algorithm, keySize, salt);
IvParameterSpec ivParamSpec = new IvParameterSpec(iv);
// Perform decryption using the Cipher
cipher.init(Cipher.DECRYPT_MODE, key, ivParamSpec);
decryptedMessage = cipher.doFinal(encryptedMessageKernel);
// Return the results
return decryptedMessage;
}
public static void main(String[] args) throws Exception {
// allow the use of the BC JCE
Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
final String message = "Secret Message";
PBESample engine = new PBESample();
byte[] encryptedMessage = engine.encrypt("AES", 128, message.getBytes());
byte[] decryptedMessage = engine.decrypt("AES", 128, encryptedMessage);
if (message.equals(new String(decryptedMessage))) {
System.out.println("AES OK");
}
encryptedMessage = engine.encrypt("Serpent", 256, message.getBytes());
decryptedMessage = engine.decrypt("Serpent", 256, encryptedMessage);
if (message.equals(new String(decryptedMessage))) {
System.out.println("Serpent OK");
}
encryptedMessage = engine.encrypt("TwoFish", 256, message.getBytes());
decryptedMessage = engine.decrypt("TwoFish", 256, encryptedMessage);
if (message.equals(new String(decryptedMessage))) {
System.out.println("TwoFish OK");
}
}
}