Using Base64 functions from the Apache Commons API and working with DSA I am trying to load a base 64 encoded public key from a file here is the method being used
/**
* Load a base-64 encoded public key in X.509 format
* #param pkfile the name of the file containing the public key
* #return an instance of PublicKey on success or null on failure
*/
public PublicKey loadBase64PublicKey(String pkfile) {
PublicKey pub;
Base64InputStream bis;
byte[] buffer;
// load the contents of the pkfile into the buffer
try {
bis = new Base64InputStream(new FileInputStream(pkfile));
buffer = new byte[bis.available()];
bis.read(buffer);
bis.close();
} catch (Exception e) {
System.err.println(e.toString());
return null;
}
// use a KeyFactory to parse the data
try {
KeyFactory kf = KeyFactory.getInstance("DSA");
pub = kf.generatePublic(new X509EncodedKeySpec(buffer));
} catch (Exception e) {
e.printStackTrace();
return null;
}
return pub;
}
Main Method:
public static void main(String args[]) {
DigitalSignatureA DSA = new DigitalSignatureA();
// load public key
PublicKey pubKey;
pubKey = DSA.loadBase64PublicKey("sign\\pubkey-1.dat");
}
However when calling the method from main the following error comes up:
java.security.spec.InvalidKeySpecException: Inappropriate key specification: IOException: Short read of DER length
at sun.security.provider.DSAKeyFactory.engineGeneratePublic(Unknown Source)
at java.security.KeyFactory.generatePublic(Unknown Source)
at DigitalSignatureAssignment.loadBase64PublicKey(DigitalSignatureAssignment.java:147)
at DigitalSignatureAssignment.main(DigitalSignatureAssignment.java:224)
Line 147 would be pub = kf.generatePublic(new X509EncodedKeySpec(buffer));
The public key from the file is encoded in X509 but saved under base64 encoding and the Base64InputStream decodes any input.
Java InputStream.available() is never guaranteed to tell you how much (more) data exists
Note that while some implementations of InputStream will return the total number of bytes in the stream, many will not. It is never correct to use the return value of this method to allocate a buffer intended to hold all data in this stream.
and in this commons-codec case it doesn't even try
Returns:
0 if the InputStream has reached EOF, 1 otherwise
Either pick a sufficiently large buffer size to start with, or keep expanding it and reading more until EOF, or a combination of both. Or read the file into memory as text (e.g. Files.readAllLines or Files.readAllBytes in j8+) and then decode that in-memory copy (FWIW j8+ now has java.util.Base64 and you don't need commons-codec for this)
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);
...
Following code is written to encrypt the plain text, I am using IAIK Twofish encryption/decryption code in java below sample code works fine with 128 bit key but when i try it with 192 and 156 bit key it gives an exception that java.security.InvalidKeyException: Key must be 128, 192, or 256 bit long!-
private static void doCrypto(int cipherMode, String key, File inputFile, File outputFile) throws CryptoException {
try {
SecretKey secretKey = new SecretKeySpec(key.getBytes(), ALGORITHM);
Cipher cipher = Cipher.getInstance(TRANSFORMATION, "IAIK");
cipher.init(cipherMode, secretKey);
FileInputStream inputStream = new FileInputStream(inputFile);
byte[] inputBytes = new byte[(int) inputFile.length()];
inputStream.read(inputBytes);
byte[] outputBytes = cipher.doFinal(inputBytes);
FileOutputStream outputStream = new FileOutputStream(outputFile);
outputStream.write(outputBytes);
inputStream.close();
outputStream.close();
} catch (NoSuchPaddingException | NoSuchAlgorithmException | InvalidKeyException | BadPaddingException
| IllegalBlockSizeException | IOException ex) {
throw new CryptoException("Error encrypting/decrypting file", ex);
} catch (NoSuchProviderException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
for above method when I am giving 128 bit key it works fine as below,
KeyGenerator keyGen = KeyGenerator.getInstance("Twofish", "IAIK");
keyGen.init(192);
txtSecretKey.setText(iaik.utils.Util.toString(key.getEncoded()));
SekertKey key = key.generateKey();
encrypt(txtSecretKey.getText(), inputFile, encryptedFile);
Caused by: java.security.InvalidKeyException: Key must be 128, 192, or 256 bit long!
at iaik.security.cipher.N.a(Unknown Source)
at iaik.security.cipher.i.a(Unknown Source)
at iaik.security.cipher.a.engineInit(Unknown Source)
at javax.crypto.Cipher.init(Cipher.java:1249)
at javax.crypto.Cipher.init(Cipher.java:1189)
at com.opensourse.crypto.twofish.CryptoUtils.doCrypto(CryptoUtils.java:38)
In your main method you're transforming the SecretKey to a String that is shown in a (GUI) textfield. Printing out the contents of the key looks like:
key in hex: 7b44a1f09136a248a40c8043fa02fbcf
textfield : 7B:44:A1:F0:91:36:A2:48:A4:0C:80:43:FA:02:FB:CF
Converting this String in the textfield back to a byte[] to regenerate the secretKey with ".getBytes" will fail as the colon chars will be decoded as well:
SecretKey secretKey = new SecretKeySpec(key.getBytes(), ALGORITHM)
The IAIK-Util-class provides a ".toByteArray" method that simply ignores other chars than '0-9' and 'a-f', see the documentation
in http://javadoc.iaik.tugraz.at/iaik_jce/current/iaik/utils/Util.html:
Convert the given string with hex values to a byte array. For example "001122" is turned into {0, 0x11, 0x22}. All characters outside the range of '0'-'9', 'a'-'z', and 'A'-'Z' or simply ignored.
Simply change the line in doCrypto-method and all is working:
SecretKey secretKey = new SecretKeySpec(iaik.utils.Util.toByteArray(key), ALGORITHM);
Make sure you have the "java cryptography extension (jce) unlimited strength jurisdiction policy files 8" from here. See this for instructions.
It's always good to doublecheck some answers as the error "128bit aes key is working, 192/256 keys not" is
symptomatic for a limited cryptographic policy.
Please run this little program and show us the results on console ("false" means unlimited crypto policy...)
import javax.crypto.Cipher;
import java.security.NoSuchAlgorithmException;
public class Main {
public static void main(String[] args) {
System.out.println("\nTest with Java version: " + Runtime.version());
System.out.println("Java restricted cryptography: " + restrictedCryptography());
}
/**
* Determines if cryptography restrictions apply.
* Restrictions apply if the value of {#link Cipher#getMaxAllowedKeyLength(String)} returns a value smaller than {#link Integer#MAX_VALUE} if there are any restrictions according to the JavaDoc of the method.
* This method is used with the transform <code>"AES/CBC/PKCS5Padding"</code> as this is an often used algorithm that is an implementation requirement for Java SE.
*
* #return <code>true</code> if restrictions apply, <code>false</code> otherwise
*
* code by Maarten Bodewes, https://stackoverflow.com/questions/7953567/checking-if-unlimited-cryptography-is-available#
*/
public static boolean restrictedCryptography() {
try {
return Cipher.getMaxAllowedKeyLength("AES/CBC/PKCS5Padding") < Integer.MAX_VALUE;
} catch (final NoSuchAlgorithmException e) {
throw new IllegalStateException("The transform \"AES/CBC/PKCS5Padding\" is not available (the availability of this algorithm is mandatory for Java SE implementations)", e);
}
}
}
So I have a server side public key and private key, my aim is to send the client the public key, the client will encrypt a string with the key, then send the bytes through a stream, and the server will decrypt the byte array.
Exception:
javax.crypto.BadPaddingException: Decryption error
Code:
Sending the encoded key.
handler.getOos().writeObject(publicKey.getEncoded());
handler.getOos().flush();
Receiving the byte array (of the encoded key):
Object o = ois.readObject();
if (o instanceof byte[]) {
JChat.get().setServerPublicKey(KeyFactory.getInstance("RSA").generatePublic(new X509EncodedKeySpec((byte[]) o)));
JChat.get().go();
}
The go() method (here I use a DataOutputStream to send the byte array):
public void go() {
String text = "hello darkness my old friend";
byte[] encrypted = encrypt(text, serverPublicKey);
try {
handler.getDos().write(encrypted);
handler.getDos().flush();
} catch (IOException e) {
e.printStackTrace();
}
}
Reading the byte array, on the server side:
int count = dis.available();
byte[] in = new byte[count];
dis.readFully(in);
System.out.println(Server.decrypt(in, Server.get().getPrivateKey()));
The decryption method throws this exception:
javax.crypto.BadPaddingException: Decryption error
at sun.security.rsa.RSAPadding.unpadV15(RSAPadding.java:380)
at sun.security.rsa.RSAPadding.unpad(RSAPadding.java:291)
at com.sun.crypto.provider.RSACipher.doFinal(RSACipher.java:363)
at com.sun.crypto.provider.RSACipher.engineDoFinal(RSACipher.java:389)
at javax.crypto.Cipher.doFinal(Cipher.java:2165)
at com.archiepking.Server.decrypt(Server.java:97)
at com.archiepking.net.ClientHandler$1.run(ClientHandler.java:44)
at java.lang.Thread.run(Thread.java:745)
Any suggestions as to what I am doing wrong? Please note:
Dos = DataOutputStream Dis = DataInputStream Oos = ObjectOutputStream
Ois = ObjectInputStream
I am using two different sockets, one for sending objects and one for datatypes (as my chat application will need both).
What can I do to fix this error?
FURTHER INFORMATION:
Generation of keys:
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
keyPairGenerator.initialize(1024);
KeyPair keyPair = keyPairGenerator.genKeyPair();
byte[] publicKeyBytes = keyPair.getPublic().getEncoded();
FileOutputStream fosPublic = new FileOutputStream("public");
fosPublic.write(publicKeyBytes);
fosPublic.close();
byte[] privateKeyBytes = keyPair.getPrivate().getEncoded();
FileOutputStream fosPrivate = new FileOutputStream("private");
fosPrivate.write(privateKeyBytes);
fosPrivate.close();
publicKey = keyPair.getPublic();
privateKey = keyPair.getPrivate();
The problem is that you are using DataInputStream.available() to determine how many bytes to read. That method does not do what you apparently think that it does.
From the Javadoc of this method:
Returns an estimate of the number of bytes that can be read (or
skipped over) from this input stream without blocking by the next
caller of a method for this input stream. The next caller might be the
same thread or another thread. A single read or skip of this many
bytes will not block, but may read or skip fewer bytes.
It just returns the number of bytes that can be read without blocking, which can be far less than the actual number of bytes that you sent, especially if you are using network Sockets to send/receive that data.
The solution:
before writing the bytes, write an int with the writeInt method that contains the number of bytes that you're writing
before reading the bytes, call readInt to read the number of bytes that will follow, and construct a byte array of the right length from that number.
If you are using an ObjectOutputStream why bother converting the public key to a byte array using getEncoded? You can serialize the object directly. e.g.
handler.getOos().writeObject(publicKey);
Or if you have to use the encoded version, then remove the ObjectOutputStream and use ByteArrayOutputStream instead.
I'm currently working on an Java-Cyrpto-API which I want to include in an Android app later on. I tested every function of my Crypto-API and after all unit test succeeded I decided to include my jar into an Android project.
In the project I started generating a 4096-bit key pair in order to add it to an object in my class.
RSA.RSAKeyPair keyPair = null;
try {
keyPair = RSA.generateKeyPair(4096);
} catch (IOException e) {
e.printStackTrace();
}
self.setPrivateKey(keyPair.getPrivateKey());
self.setPublicKey(keyPair.getPublicKey());
Afterwards I call a function in my API which uses data from the "self" object to encrypt some data.
The app throws me the following exception when it tries to encrypt some data with RSA.
03-15 02:39:16.769 2394-2414/de.ifasec.instari E/Testīš javax.crypto.IllegalBlockSizeException: input must be under 512 bytes
at com.android.org.conscrypt.OpenSSLCipherRSA.engineDoFinal(OpenSSLCipherRSA.java:245)
at javax.crypto.Cipher.doFinal(Cipher.java:1340)
at com.instari.encryption.RSA.encryptWithPublic(RSA.java:91)
I used Google to find out whats going wrong here an only found posts about invalid key lengths. I used the debugger to get all keys and values I generated in the app to test them directly in my API. My API tests succeeded without any errors.
Does Android have any restrictions or problems with RSA-Encryption?
Edit:
private static final String CIPHER_ALGORITHM = "RSA/ECB/PKCS1Padding";
This is my encryptWithPublic() Method:
// initialize
byte[] byteData = data.getBytes(); // convert string to byte array
PublicKey keyObject = extractPublicKey(publicKey);
// encrypt
Cipher cipher = null; // create conversion processing object
try {
cipher = Cipher.getInstance(CIPHER_ALGORITHM);
} catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
e.printStackTrace();
return null;
}
cipher.init(Cipher.ENCRYPT_MODE, keyObject); // initialize object's mode and key
byte[] encryptedByteData = cipher.doFinal(byteData); // use object for encryption
return Base64.encode(encryptedByteData);
RSA.RSAKeyPair is a simple class i added to store the key:
public static class RSAKeyPair{
private String privateKey;
private String publicKey;
private RSAKeyPair(String privateKey, String publicKey) {
this.privateKey = privateKey;
this.publicKey = publicKey;
}
public String getPrivateKey() {
return privateKey;
}
public String getPublicKey() {
return publicKey;
}
}
The object self is similar to this. It just returns the keys I added before.
It seems you are just trying to encrypt too much data. The amount of data that can be encrypted using RSA with PKCS#1 padding is the key size (512 bytes) minus the padding overhead of 11 bytes, making for a total of 501 bytes. This is true for both Android, but also for Java SE.
With Java, the "ECB" part is a bit of a misnomer. RSA doesn't use any mode of operation, so it should have been "None". Only one block of plaintext will be encrypted. If you want to encrypt more, you can first generate a random AES key, use that to encrypt the message, and subsequently encrypt the random key using RSA. This is called hybrid encryption.
I am not an expert in cryptography and I am getting some interesting results when I use the encryption method below.
The server is .NET C# and the client runs JAVA. Basically, We encrypt credit card information and for the 12 credit cards I have, 11 works perfectly with the methods below.
However, one of the cards (real VISA credit CARD) the result returned by encrypt() and converted to hex has a negative symbol in the start of the string, like this:
-6d9830a52b2c3add7a78fd9897bca19d....., it fails when the server tries to decrypt it and I think it should be positive not negative based on this explanation RSA - Encryption with negative exponent
private static byte[] encrypt(String text, PublicKey pubRSA) throws Exception
{
Cipher cipher = Cipher.getInstance(RSA);
cipher.init(Cipher.ENCRYPT_MODE, pubRSA);
return cipher.doFinal(text.getBytes());
}
//Using this encryption method one card could not be decrypted by vPAY due to negative (exponential) symbol.
//It may have the same affect with other cards
public final static byte[] encrypt(String text)
{
try {
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
X509EncodedKeySpec x509Spec = new X509EncodedKeySpec(Base64.decode(pkBase64));
PublicKey pk = keyFactory.generatePublic(x509Spec);
return encrypt(text, pk);
}
catch(Exception e)
{
e.printStackTrace();
}
return null;
}
Has anyone faced something like that and found a workaround?
I have tried three other algorithms with different KeySpec and the same publicKey (the source is a string in base64 format) but none of them could be decrypted by the server even with the cards the were working before...
UPDATE 1
This is how a convert the encrypted result in bytes to HEX:
public static String byteToHex(byte[] string)
{
try {
return String.format("%04x", new BigInteger(string));
} catch (Exception e) {
// TODO Auto-generated catch block
return null;
}
}
You should print out the hexadecimal string directly from byte[]. This can be done using the following code:
StringBuilder sb = new StringBuilder(data.length * 2);
for (int i = 0; i < data.length; i++) {
sb.append(String.format("%02X", data[i] & 0xFF));
}
return sb.toString();
There is no need to use BigInteger. In fact, it is dangerous to use BigInteger. One reason is the one you've already encountered: BigInteger conversion to/from byte[] is using signed big endian encoding by default. The other thing is that the output of the RSA signature (as integer) may be smaller than the modulus size in hexadecimals. This is why EJP's solution will fail now and then.
RSA output has been defined in bytes, as an unsigned big endian encoded in the same number of bits as the key size (using integer to octet string encoding in the standard documents).
public static String byteToHex(byte[] string)
A byte[] is not a string. It's a byte array. Don't confuse yourself with inappropriate variable names. String is not a container for binary data.
return String.format("%04x", new BigInteger(string));
Try return new BigInteger(1,string).toString(16), and have a look at the Javadoc to see why this works where new BigInteger(string) didn't.