I'm reverse engineering some code which is decrypting data, hoping I'll be able to encrypt it back and obtain the same data it started with, for reasons that would make this question too long and off-topic.
public void Test() throws Exception {
String pk_enc = //...
String hashStr_64 = //...
byte[] hashStr_encrypted = Base64.decode(hashStr_64);
X509EncodedKeySpec e = new X509EncodedKeySpec(Base64.decode(pk_enc));
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
RSAPublicKey RSApublicKey = (RSAPublicKey) keyFactory.generatePublic(e);
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1PADDING");
cipher.init(2, RSApublicKey); // '2' means decrypt
byte[] hashStr_decrypted = cipher.doFinal(hashStr_encrypted);
String hashStr_result = new String(hashStr_decrypted);
// Now in reverse...
Cipher cipher1 = Cipher.getInstance("RSA/ECB/PKCS1PADDING");
// instantiating a new cipher or using the original one makes no difference
cipher1.init(1, RSApublicKey); // '1' means encrypt
byte[] hashStr_encrypted_reverse = cipher1.doFinal(hashStr_decrypted);
String hashStr_64_reverse = Base64.encode(hashStr_encrypted_reverse);
}
All the code before // Now in reverse... cannot be changed, but that doesn't mean it's impossible to convert hashStr_result back to hashStr_64, right?
However, the code I've wrote after, that should do just that, doesn't work.
hashStr_encrypted_reverse is different from hashStr_encrypted. Why is that and how can I fix it?
Another sign that something went wrong in the encryption is what happens if I try to decrypt again...
// Decrypt again
Cipher cipher2 = Cipher.getInstance("RSA/ECB/PKCS1PADDING");
cipher2.init(2, RSApublicKey);
byte[] hashStr_decrypted_again = cipher.doFinal(hashStr_encrypted_reverse);
This throws:
javax.crypto.BadPaddingException
I don't really care, but maybe it could help answer the question.
Terminology will be confusing. There are 4 RSA operations, best described as: signing, verifying, encrypting, decrypting. Mapping these to a lower-level and using only the language of encryption and decryption, these map as follows:
sign-verify pair
signing -> encrypt with private key
verifying -> decrypt with public key
encrypt-decrypt pair
encrypting -> encrypt with public key
decrypting -> decrypt with private key.
As you can see, each pair of operations has the private key on one side and the public key on the other.
As #JamesKPolk said in his comment, this isn't how RSA works. RSA is an asymmetric encryption algorithm: there are two keys, public and private. A symmetric algorithm (e.g., AES) has a single key, which is used for both encryption and decryption, and that key must be kept safe, except to the sending and receiving parties.
Why asymmetric encryption?
You can encrypt with a public key (typically someone else's key that they've shared with you), and they must use their private key to decrypt it. Anyone can have the public key (that's why it's public), but it cannot be used to read the encrypted message. This is where you are having your problem.
Also, you can encrypt a message with a private key (typically your own), and anyone else can use your public key to decrypt it. This is how digital signatures are implemented: for example, you would encrypt a digest of a document, and anyone can verify your signature if they have your public key, but no one else could have signed it.
Related
Every time the encryption values changed by using AES, let anyone investigate the below code and let me know the issue
code:
private static final String secretKeys = "58BA833E57A51CBF9BF8BAB696BF9"
public static String encrypt() throws Exception {
byte[] salt = new byte[16];
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
PBEKeySpec pbeKeySpec = new PBEKeySpec(secretKeys.getChars(),salt,1000, 256);
Key secretKey = factory.generateSecret(pbeKeySpec);
byte[] key = new byte[32];
byte[] iv = new byte[16];
SecretKeySpec secret = new SecretKeySpec(key, "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, secret);
byte[] result = cipher.doFinal("welcome".getBytes("UTF-8"));
String s = Base64.getEncoder().encodeToString(result);
return s
}
Output
first time I got the below-encrypted string
CZRIP35M4CnJtuDQ6YpmaQ==
The second time I got the below-encrypted string
/fylTjohAZDsnCaHhiZo3A==
I have three questions:
why the encrypted string not a constant?
how can I set the Blocksize? ( AES.BlockSize = 128;)
How can I set the padding mode? (AES.Padding = PaddingMode.PKCS7;)
For the first question, #Freiheit already answered this.
Long story short, based on the iv (initilization vector) which acts as a salt and will be different for each encryption.
Having that said, encrypting the same plain text will result in different encrypted text, but the decryption (if necessary) will result back into the same plain text.
IV is helpful to make the encryption predictable.
Having stored the same password for 2 different users in a database will have different values, but will be the same password.
With the current cipher configured, you already have 128 block size. You can read more about the different cypher transformation here. You can also find more information of the block sizes for different algorithms here
You just need to change the Cipher.getInstance() to AES/CBC/PKCS7Padding
1) the encrypted text is always different because the Cipher initialization is providing it's own IV since you are not providing one. You need to provide the IV you've "computed" in order to have a consistent output. Remember you never want to use an IV more than once for whatever this code is ultimately intended to do.
2) The keysize can be 128, 192 or 256 but the blocksize is always 128.
3) Java only provides PKCS5, but there is no difference in the implementation for AES. see what-is-the-difference-between-pkcs5-padding-and-pkcs7-padding
As was already pointed out there are several problems with the code provided such as the first lines not actually doing anything and the key and iv both being uninitialized. I would additionally suggest you use SecureRandom to initialize your key and iv. If you plan on using only a single AES key, this can be computed once and placed in the code or configuration file instead of running PBKDF2 every time.
Only adding to the answer provided by #micker, you need to invoke another version of Cipher.init(); one that takes the IV into account:
...
byte[] iv = new byte[16];
IvParameterSpec ivSpec = new IvParameterSpec(iv); // <= Wrap your IV bytes here.
SecretKeySpec secret = new SecretKeySpec(key, "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, secret, ivSpec); // <= Add IV here.
...
That being said, the implementation suffers from a slew of other issues (key being all zeroes, IV being all zeroes, first 4 line don't do anything for you (as #JBNizet pointed out)). I hope you are only using it to study how Java's encryption mechanics works.
I'm working on an android application where I'd like the user to be able to encrypt messages using other's public keys. The system would generate a public/private keypair and then messages can be sent to other users secretly.
I'm creating an Encryption class which will handle the encryption/decryption of messages. Unfortunately I'm having some problems.
In this method, I'd like to pass the user's secret (private key) as well as the message they want to encrypt. I'd like the secret to be user-defined (like "MySecretPassword").
public static void lock(String secret, String textToEncrypt) {
try {
//Convert the public key string into a key
byte[] encodedPublicKey = Base64.decode(secret.getBytes("utf-8"),Base64.DEFAULT);
X509EncodedKeySpec spec = new X509EncodedKeySpec(encodedPublicKey);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PublicKey publickey = keyFactory.generatePublic(spec); //Crash Here
PrivateKey privateKey = keyFactory.generatePrivate(spec);
//Encrypt Message
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.ENCRYPT_MODE, publickey);
byte[] encryptedBytes = cipher.doFinal(textToEncrypt.getBytes());
Log.d(TAG,"Encrypted: "+new String(encryptedBytes));
} catch (Exception e) {
e.printStackTrace();
}
}
The exception is as follows:
java.security.spec.InvalidKeySpecException: java.lang.RuntimeException: error:0c0740b0:ASN.1 encoding routines:ASN1_get_object:TOO_LONG
What am I missing here? Am I missing something obvious or am I misunderstanding how these tools work? I've used this javascript library for public/private key encryption before and am trying to do something similar here. I'd appreciate it if somebody could point me in the right direction :)
A secret is not a public key.
You encrypt with the public key of the recipient. That value is public, which means that anybody can look it up. You need to get the value of the other party's public key and feed it into your code, not send in your own private key. The proper way to do this does not involve any secrets!
Normally one does not directly encrypt a message with RSA, instead they encrypt an AES key (or other symmetric key) with RSA and use the AES key to encrypt the message. If your messages are really short, you could use RSA directly, but it won't work for long messages.
Here are a couple links showing how to implement RSA on Android:
RSA using SpongyCastle
RSA encryption in Android and Java
I have a system that requires a RSA keypair to be generated in javascript, have the public key then stored in a database at the server side (as a string), then the server side which is in Java will encrypt a string with the stored public key and send it to the client side which will decrypt the string with the private key.
I'm using a browsified version of node-rsa on my client browser.
First at the client i generate a keypair and export the keys, storing them as strings
var NodeRSA = require('node-rsa');
var key = new NodeRSA({b: 1024});
key.exportKey("pkcs8-private");
key.exportKey("pkcs8-public-pem");
The exported private key is stored at the client and the public at the server
Next i used java to encrypt a string with the public key received, so i parse the pkcs8 public key into a Java PublicKey object.
String pubKey = "<Retrieved pkcs8 public key>";
pubKey = pubKey.replaceAll("(-+BEGIN PUBLIC KEY-+\\r?\\n|-+END PUBLIC KEY-+\\r?\\n?)", "");
byte[] keyBytes = Base64.decodeBase64(pubKey);
X509EncodedKeySpec spec = new X509EncodedKeySpec(keyBytes);
KeyFactory kf = KeyFactory.getInstance("RSA");
PublicKey pk = kf.generatePublic(spec);
And encrypt a text with it
byte[] cipherText;
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.ENCRYPT_MODE, pk);
cipherText = cipher.doFinal("Hello World!".getBytes());
return Base64.encodeBase64String(cipherText);
Which works nicely and returns me a Base64 encoded encrypted string like this
WTS1J2f4w5icsUOCtulyHDaBmB5lN7D8mnj0QWMDBkUGiPHkM8nHVx9pd0MtbQAQNasQS2X8kisLMYyEMPasFZtDH0zX1e8lNYaW0xMKsg++ge87f+95nl+TmxDy6S1m7Ce/n0wXno+0MbSv8YsJtsUcAleyyfQX2bxqX8u7Gjs=
Then i try to decrypt it the string at the client side
First i reimport the stored keys in node-rsa
var NodeRSA = require('node-rsa');
var key = new NodeRSA();
key.importKey("<exported private key string>","pkcs8-private");
key.importKey("<exported public key string>","pkcs8-public-pem");
Then i try to decrypt the Base64 encoded encrypted string
key.decrypt("<Base64 Encoded Encrypted>", 'utf-8');
This is where the problem happens, javascript throws this error
Uncaught Error: Error during decryption (probably incorrect key). Original error: Error: Error decoding message, the lHash calculated from the label provided and the lHash in the encrypted data do not match.(…)
However i have tested that if i encrypt and decrypt the text just within javascript, it works just fine. This makes me think that it's some difference between the way i encrypted it at java and how it's done at javascript
Could anyone point out the mistake that I've made here please?
Oh i found the solution. It was a difference in the encryption method.
I just had to initialize Cipher with
Cipher.getInstance("RSA/ECB/OAEPWithSHA1AndMGF1Padding");
instead of
Cipher.getInstance("RSA");
to match node-rsa
Instead of change the encryption mode on my backend, I change it on front.
const rsa = new NodeRSA({ b: 2048 });
rsa.setOptions({ encryptionScheme: 'pkcs1' });
rsa.importKey(
'key',
'pkcs8-public',
);
and keep using Cipher.getInstance("RSA"); to encrypt and decrypt on backend.
I have some data from a external party which is encrypted according to them in: 'Rijndeal 256 with the private key'
Alongside these records there are a public and private key certificate which look like RSA certificates.
From what i've learned so far it seems the common way to use encryption with certifcates is to generate a 'secret key' or some kind in initialization vector and use this to encrypted text. So i'm thinking this is probably what they have done (the data was encrypted by a PHP application)
I'm trying to decrypt this text with javax.crypto.Cipher but i think i problably need more information on the specific encryption, but i dont really know what information to ask for, and think its likely the 'default options' will probably work. (Communication with the supplying party is difficult and slow).
i'm currently Using the following code to get the private key:
InputStreamReader ir = new InputStreamReader(the_inputstream_for_the_private_key_record);
Security.addProvider(new BouncyCastleProvider());
pemr = new PEMReader(ir);
Object o = pemr.readObject();
keyPair kp = (KeyPair) o;
return kp.getPrivate();
This seems to work as i get a instantiated PrivateKey object without errors the toString looks like:
RSA Private CRT Key
modulus: c98faa50ba69<trimmed>
public exponent: 10001
private exponent: bb889fbe5cb2a6763f...<trimmed>
primeP: eb73e85dc636f5751b...<trimmed>
primeQ: db269bd603a2b81fc9...<trimmed>
primeExponentP: 85b9f111c190595cc8...<trimmed>
primeExponentQ: a66d59a75bb77530de...<trimmed>
crtCoefficient: 79415b078c4c229746...<trimmed>
For each record i also have a entry like the following:
{
"decryptedLength":128389,
"symKeyLength":32,
"symKey":"SImE8VnSZaAu1Ve...<trimmed (this is always 685 chars long) >...ayaJcnpSeOqAGM7q="
}
Basically this is where i'm a bit stuck.
My guess would be that that 'symkey' value is encrypted with RSA which in turn when decrypted would yield the secretKey for the AES part, but if i try:
Cipher rsaCipher = Cipher.getInstance("RSA");
rsaCipher.init(Cipher.DECRYPT_MODE, key);
byte[] b = rsaCipher.doFinal('symkey'.getbytes());
this gets me "javax.crypto.IllegalBlockSizeException: Data must not be longer than 512 bytes", which seems logical since this string is 685characters long
I'm probably missing something very obvious here...
Any suggestions are appreciated.
Just guessing, but I think the value
"symKey":"SImE8VnSZaAu1Ve...<trimmed (this is always 685 chars long) >...ayaJcnpSeOqAGM7q="
is the base64 encoded output from RSA encryption using a 4096-bit public key. You need to first base64 decode the value into a byte[] array, then decrypt it with the private key, the result of which will be a 256-bit key. Note that "Rijndael 256" is ambiguous, since Rijndael supports both a 256 bit blocksize and also a 256 bit keysize.
with GregS's answer i finaly got this to work.
(adding an answer in case someone else needs to decrypt similar php encoded stuff).
The first part was to decrypt de symmetricKey ("symkey") from the metaData string
This was as Greg notes a Base64 encoded, RSA encrypted key which was decoded like so:
Cipher rsaCipher = Cipher.getInstance("RSA");
rsaCipher.init(Cipher.DECRYPT_MODE, key);
byte[] encryptedRijndaelKey = Base64.decodeBase64(base64EncodedSymetricKey); //from the metaData
byte[] rijndaelKeyBytes = rsaCipher.doFinal(encryptedRijndaelKey);
This Rijndael key was then used to decrypt de actual encrypted data like so:
RijndaelEngine rijndaelEngine = new RijndaelEngine(256); // *1 *2
KeyParameter keyParam = new KeyParameter(rijndaelKeyBytes)
rijndaelEngine.init(false, keyParam); //false == decrypt
PaddedBufferedBlockCipher bbc = new PaddedBufferedBlockCipher(rijndaelEngine, new ZeroBytePadding()); // *3
byte[] decryptedBytes = new byte[decryptedLenght]; //from the storageOptions string
int processed = bbc.processBytes(inputBytes, 0, inputBytes.length, decryptedBytes, 0);
bbc.doFinal(decryptedBytes, processed);
*1 because the Sun JCA only supports common AES which has a 128bits keysize i had to use a different provider (BouncyCastle).
*2 apparently the blocksize was also 256 bits (trail & error)
*3 apparently there was no padding used, thus the ZeroPadding for padding (again trail & error).
The symKey value is Base64 encoded and must be decoded before you can do the private key decryption on it. Also, the symmetric encryption sounds like it is AES-256. (AES is based on the Rijndael cipher).
I am trying to encrypt and decrypt a message as mentioned in the below code. Basically I want to encrypt a message with a public key and convert that encrypted message from byte array to String. And decrypt this string into original text. Here are the both methods. Here encryption works fine but decryption fails (error is "Data must start with zero"). I think this is causing because I convert encrypted byte array into String.
How do I solve this? (I want to have encrypted byte array as string and use it for decryption) Is there any other approach (with public and private keys)
public static String getEncryptedMessage(String publicKeyFilePath,
String plainMessage) {
byte[] encryptedBytes;
try {
Cipher cipher = Cipher.getInstance("RSA");
byte[] publicKeyContentsAsByteArray = getBytesFromFile(publicKeyFilePath);
PublicKey publicKey = getPublicKey(publicKeyContentsAsByteArray);
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
encryptedBytes = cipher.doFinal(plainMessage.getBytes());
return new String(encryptedBytes);
} catch (Throwable t) {
}
}
public static String getDecryptedMessage(
String privateKeyFilePath, String encryptedMessage)
{
byte[] decryptedMessage;
try {
Cipher cipher = Cipher.getInstance("RSA");
byte[] privateKeyContentsAsByteArray = getBytesFromFile(privateKeyFilePath);
PrivateKey privateKey = getPrivateKey(privateKeyContentsAsByteArray);
cipher.init(Cipher.DECRYPT_MODE, privateKey);
decryptedMessage = cipher.doFinal(encryptedMessage.getBytes());
return new String(decryptedMessage);
} catch (Throwable t) {
}
If you look at this page (http://www.wikijava.org/wiki/Secret_Key_Cryptography_Tutorial) you will need to do base-64 encoding to turn the bytes into a string, then to decrypt it you would just decode it then decrypt.
Base-64 encoding uses the first 7 bits of a byte, to make something that is printable or emailable, for example.
UPDATE:
I made a mistake, there are 64 characters that it would be encoded in, again, in order to make it easier to use as something printable.
Why don't you treat the message as byte array from encryption to decryption? Why changing it to String in the middle? (I know it seems like a question, but it's actually an answer...)
Using RSA directly on unformatted data may leave your application vulnerable to an adaptive chosen ciphertext attack. For details please see Chapter 8, pages 288-289, of the Handbook of Applied Cryptography, a freely-available book from CRC Press. (It's well worth buying the bound edition, if you're really interested in cryptography -- you'll be stunned at the quality for the price.)
Because of this attack, most protocols that integrate RSA use RSA for encrypting randomly-generated session keys or signing hash functions with outputs that ought to be indistinguishable from random, OR using very carefully formatted messages that will fail to be correctly interpreted. (See Note 8.63 in HAC for details.)