Related
We use BouncyCastle PBEWITHSHA256AND256BITAES-CBC-BC to encrypt data with our java application and store the encrypted result in a MySql Database.
Example Code:
StandardPBEStringEncryptor configurationEncryptor = new StandardPBEStringEncryptor();
configurationEncryptor.setAlgorithm("PBEWITHSHA256AND256BITAES-CBC-BC");
configurationEncryptor.setProviderName("BC");
configurationEncryptor.setSaltGenerator(new RandomSaltGenerator());
configurationEncryptor.setKeyObtentionIterations(1000);
configurationEncryptor.setPassword("aTestPassword");
String input = "A Test String!";
String cypherText = configurationEncryptor.encrypt(input);
String plainText = configurationEncryptor.decrypt(cypherText);
System.out.println("Input:" + input + " cypher:" + cypherText + " plain:" + plainText);
Output:
Input:A Test String! cypher:DhCSPbCWcZ76TUD/dDeGczlHbI9dQJyB2lKAiL7dDEk= plain:A Test String!
The cypher string above is a base64 encoded string which we store in our database.
I would now like to attempt to decrypt the cypher string stored in our database using the AES utilities provided by MySql.
I am trying to understand how the BC provider concatenates the encrypted data so that I can split it up and recreate the required parameters to enable me to decrypt the data with other tools - in this case MySql's AES_DECRYPT function.
Inspecting the code I can see that the first 16bytes of the cypher text (when base 64 decoded) is the salt, I am unsure where the init vector (IV) is stored in the remainder of the cypher text data.
If we can parse out the IV, salt and encrypted value from the string, then it should be possible to use external tools to decrypt the data.
A sample Mysql AES usage is as follows:
SET block_encryption_mode = 'aes-256-cbc';
SET #key_str = SHA2('aTestPassword',256);
SET #init_vector = RANDOM_BYTES(16);
SET #crypt_str = AES_ENCRYPT('A Test String!',#key_str,#init_vector);
SELECT AES_DECRYPT(#crypt_str,#key_str,#init_vector);
Output:
A Test String!
I would like to know how to parse the BouncyCastle cypher text to obtain its component parts, and also how to use the salt to generate the correct key hash with the number of iterations specified for use by Mysql to decrypt the data.
Any help much appreciated!
This answer is not a solution in code but will help you in finding the code.
First: you are NOT using Bouncy Castle to en-/decrypt directly - of course the cipher is used as provider for the en-/decryption.
The library that does the complete en-/decryption is JASYPT and here we can find answers for your question.
Base for my research is the GitHub https://github.com/jboss-fuse/jasypt/tree/master/jasypt/src/main/java/org/jasypt/encryption/pbe and I'm starting with "StandardPBEStringEncryptor.java":
As we are trying to understand the encryption in use I found
// The StandardPBEByteEncryptor that will be internally used.
private final StandardPBEByteEncryptor byteEncryptor;
and later the encrypt-method:
...
// The StandardPBEByteEncryptor does its job.
byte[] encryptedMessage = this.byteEncryptor.encrypt(messageBytes);
...
if (this.stringOutputTypeBase64) {
encryptedMessage = this.base64.encode(encryptedMessage);
result = new String(encryptedMessage,ENCRYPTED_MESSAGE_CHARSET);
} else {
result = CommonUtils.toHexadecimal(encryptedMessage);
}
As you get a Base64-encoded string this class just returns the encryptedMessage in Base64-encding.
Let's see the base class "StandardPBEByteEncryptor.java":
Searching for ivInUse:
// Initialization Vector to be used for encryption and decryption.
private byte[] ivInUse = null;
...
// Initialize Initialization Vector
this.ivInUse = new byte[algorithmBlockSize];
That means we do have a static IV of 16 bytes length (blocklength for AES) filled with "x00".
salt:
The DefaultSaltLength is set to 8 but when using a block cipher the salt length equals to the cipher block size (for AES 16):
// The salt size for the chosen algorithm is set to be equal
// to the algorithm's block size (if it is a block algorithm).
final int algorithmBlockSize = this.encryptCipher.getBlockSize();
if (algorithmBlockSize > 0) {
this.saltSizeBytes = algorithmBlockSize;
}
The salt is generated with the saltGenerator and after encryption it is concatenated with the ciphertext in the form salt|encryptedMessage:
encrypt:
...
// Finally we build an array containing both the unencrypted salt
// and the result of the encryption. This is done only
// if the salt generator we are using specifies to do so.
if (this.saltGenerator.includePlainSaltInEncryptionResults()) {
// Insert unhashed salt before the encryption result
return CommonUtils.appendArrays(salt, encryptedMessage);
}
The number of iterations is given by initialization (1000).
Last part to solve is the algorithm for the cipher-init and when using OpenJava 11 I find:
PBEWithHmacSHA256AndAES_256
that (hopefully) works in CBC-mode.
I am looking for a solution that meets the following requirements:
Let's assume, there are: the Application installed on a computing device and controlling it, Users that use this application, and Maintainers, that provide some support for the application. Application has the Configuration, for example in the file or database. Configuration is updated manually by Maintainers when required, for example weekly. Configuration contains, for example, list of emails, Application sends it's alerts to. Let's assume, that it is not possible for Users to modify the Application in any way. Although, Application is written in Java, so it is easy for Users to copy and debug it. Internally, the Application decrypts the Configuration in the Application's memory, in order to use the Configuration.
Users shall able to view the Configuration from inside the Application. Users shall be unable to change the Configuration, or to use their own (which is basically the same), for example to change any email or remove existing email or add a new one.
Additional requirement, that is not mandatory: It shall not be possible to directly view the Configuration without the Application. I understand it's hardly really possible, so, it shall be at least just difficult, like decryption necessary to view the Configuration without the Application.
Question: how to achieve this and is it possible at all?
Possible solutions I can realize, and attacks:
1) To use some signing. To sign each Configuration with some Digest and to check the Digest in the Application then. Attack: as I understand, App shall calculate the Digest using the public key stored in it. Then the Application shall compare calculated Digest with the one provided with the Configuration. So, attack is simple : Users will modify the Configuration, then debug the Application, put a breakpoint on the place where Application has already calculated Digest for comparing it with the stored one, then Users could dump the calculated Digest and replace provided Digest with this calculated one.
2) To use hybrid encryption. In this case the attack is the same: breakpoint in the place where decrypted symmetric key is available, dump this key, then to use it for the new Configuration encryption.
3) To use asymmetric encryption. Maintainers encrypt Configuration with the public key, then Application decrypts the Configuration with the private key. Attack is simple : Users could dump private key from the Application and derive a new public key, then use it for encryption.
Is there a solution, like "encrypt with the public key, then decrypt with private" for large chunks of data (up to 10 kb), or maybe any other possible way to achieve that?
Thank you
Colleagues,
I have an application that shall receive and store some read-only data. The data shall be available for users to read but not available to change. For example, assume that there are text files with data, that keep some texts available for end users to read but not available for edits. New files shall be periodically received by the application, and shall be available for users in the same read-only mode.
So, local content (for example files content) is accessible by users. Users can copy content from application, save decrypted copies and so on. The only thing I need to prevent, is to replace existing data with any other data, including changing the content or adding new by users. I do not need changes detection, I need to make changes impossible (ok, as hard as possible).
I suppose, that the easiest way to do that, is to encrypt data with some secret key and to include the public key in the app, so app could decrypt and show the data, but without the secret key users would not be able to change the content.
I know, that standard RSA supports just a small data blocks to encrypt, usually slightly less than the key length. (I made tests and found that for RSA 2048 Java throws an exception after 254 bytes) I also read, that it's not a good idea to split source data to chunks and to encrypt these chunks then. I read that it is advised to use symmetric key, like AES, for encryption and decryption, then to encrypt this AES key with RSA key pair.
I see a big (as I suppose) security risk in this scenario - as my app is written in Java, it's quite easy to debug it and to dump the decrypted AES key, then to use it for data modifications, even without any modifications of the application itself.
So, my question is: how to solve this problem and what is considered to be secure to use in such a case?
Thank you
Update:
Of course, users are able to copy the file and to use a copy as they want. The goal is, to disallow users to change data used by application, not a copy of these data. In case of asymmetric encryption it is easy to achieve - I encrypt data with my private key, pass to app, app in runtime decrypts data with it's public key and use. In case someone would like to change data, app would not decrypt the data properly and data will be spoiled and app would fail to work till data will be reverted back.
As #President James K. Polk stated in one of his comments the only solution in my humble opinion is to sign the read-only data and use it only if the data is verified. In your "Possible solution & attack" section you write that the program compares some Digits that can easily been overwritten. Usually the signature is done with the (SHA256-)
hash of the data, but you can sign the complete data without hashing it first and 4 KB of data does not bring performance issues on my desktop Java.
I setup a full working example that simulates the maintainer-side and the app-side and as a little goodie I encrypt the plaintext with AES CBC (key generated out of signature). I know that this mode of encryption is not the "best way" as the data does not need to be kept totally secret but not direct visible it's a good solution.
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.security.*;
import java.util.Arrays;
import java.util.Random;
public class Cyptosystem {
public static void main(String[] args) throws NoSuchAlgorithmException, SignatureException, InvalidKeyException, NoSuchPaddingException, InvalidAlgorithmParameterException, BadPaddingException, IllegalBlockSizeException {
System.out.println("Cryptosystem for \nhttps://stackoverflow.com/questions/62361876/asymmetric-cryptographic-algorithm-for-large-text-data/62398723#62398723");
System.out.println("Warning: this program is experimental and has no proper exception handling");
byte[] plaintext = new byte[4000]; // content to get secured, provided by maintainers
byte[] ciphertext = new byte[0]; // encryped plaintext
byte[] dataForApp = new byte[0]; // initvector | ciphertext
new Random().nextBytes(plaintext);
// generate rsa keypair
System.out.println("generate the RSA keypair");
KeyPairGenerator rsaGenerator = KeyPairGenerator.getInstance("RSA");
SecureRandom random = new SecureRandom();
rsaGenerator.initialize(4096, random);
KeyPair rsaKeyPair = rsaGenerator.generateKeyPair();
PrivateKey rsaPrivateKey = rsaKeyPair.getPrivate(); // for signature
PublicKey rsaPublicKey = rsaKeyPair.getPublic(); // for verification, implemented in app resources
System.out.println("sign & encrypt the plaintext");
// signature done by maintainers
Signature sig = Signature.getInstance("SHA256withRSA");
sig.initSign(rsaPrivateKey);
sig.update(plaintext);
byte[] signature = sig.sign(); // provide to app as byte array, hexstring or base64 as you like
// encrypt plaintext with signature
byte[] initvector = new byte[16];
SecureRandom secureRandom = new SecureRandom();
secureRandom.nextBytes(initvector); // random initvector
// you can use another aes mode for encryption e.g. gcm
// you can use a hmac as key derivation ...
// i'm using sha256 to get a 32 byte long key
MessageDigest md = MessageDigest.getInstance("SHA-256");
byte[] aeskey = md.digest(signature);
SecretKeySpec keySpec = new SecretKeySpec(aeskey, "AES");
IvParameterSpec ivKeySpec = new IvParameterSpec(initvector);
Cipher aesCipherEnc = Cipher.getInstance("AES/CBC/PKCS5PADDING");
aesCipherEnc.init(Cipher.ENCRYPT_MODE, keySpec, ivKeySpec);
ciphertext = aesCipherEnc.doFinal(plaintext);
// copy iv | ciphertext
dataForApp = new byte[ciphertext.length + 16]; // initvector length 16 byte
System.arraycopy(initvector, 0, dataForApp, 0, initvector.length);
System.arraycopy(ciphertext, 0, dataForApp, initvector.length, ciphertext.length);
// send the dataForApp to the app (as byte array, hex string, base64 as you like
System.out.println("dataForApp length: " + dataForApp.length);
// app side, receive dataForApp & signature, already has public key
byte[] dataForAppApp = dataForApp.clone();
byte[] signatureApp = signature.clone();
System.out.println("decrypt and verify the signature");
// get initvector & ciphertext
byte[] initvectorApp = new byte[16];
byte[] ciphertextApp = new byte[(dataForAppApp.length - 16)];
System.arraycopy(dataForAppApp, 0, initvectorApp, 0, 16);
System.arraycopy(dataForAppApp,16, ciphertextApp, 0, (dataForAppApp.length - 16));
// decrypt data
MessageDigest mdApp = MessageDigest.getInstance("SHA-256");
byte[] aeskeyApp = md.digest(signature);
SecretKeySpec keySpecApp = new SecretKeySpec(aeskeyApp, "AES");
IvParameterSpec ivKeySpecApp = new IvParameterSpec(initvectorApp);
Cipher aesCipherDec = Cipher.getInstance("AES/CBC/PKCS5PADDING");
aesCipherDec.init(Cipher.DECRYPT_MODE, keySpecApp, ivKeySpecApp);
byte[] decrypttext = aesCipherDec.doFinal(ciphertextApp);
System.out.println("plaintext equals decrypttext: " + Arrays.equals(decrypttext, plaintext));
// don't use the ciphertext as the signature is not verified
Signature sigApp = Signature.getInstance("SHA256withRSA");
sigApp.initVerify(rsaPublicKey);
sigApp.update(decrypttext);
boolean signatureVerified = sigApp.verify(signatureApp);
System.out.println("signatureApp verified: " + signatureVerified);
System.out.println("if verified == true we can use the decrypttext");
}
}
Firstly, don't roll your own crypto. Cryptography is very hard, and if you make any mistake, it will have vulnerabilities you could have avoided by using a well-established library to do the heavy lifting. You could, for example, use libsodium. It has many abstractions, and probably has a solution for what you need.
With that out of the way, let's discuss how that would make it safer: the user needs to be able to read the contents, but not edit it. What exactly do you mean by "cannot edit"? Can he not be able to modify anything locally, or just not be able to upload it to your server as if he was authorized to do so?
If the former, encryption can't help you much - you need to be able to decrypt it locally, so an attacker can always dump your process' memory to get to the data - sure it would be hard, but definitely possible. Just not allowing people to edit/save/download in your application would be the strongest guarantee you can get.
If the latter, then using authentication would be the way to go - be that a simple method like HTTP basic authentication with user and password, or signing the file to be uploaded. Dealing with authentication on your application's side would be the more practical way.
Asymmetric encryption is done with the Public Key and the decryption is performed with the Private Key. As the app has to be capable to decrypt the data the app needs to know the Private Key and that's the problem with the common used algorithms, because for RSA or ECIES the Public key can get derived from the Private key. Therefore it's not a real problem to derive the Public key and store changed/appended data after encryption with the Public key.
Second thing is - you did not specify how "large" your text will be - some KB, MB, GB?
Some months ago I tested some "new" algorithms that are "Post quantum safe" and as an example I used the McEliece Fujisaki algorithm that is available with the Bouncy Castle Crypto provider (I used version 1.65, bcprov-jdk15to18-165.jar).
The program creates a 50 MB large byte array that gets encrypted with the Public key and decrypted with the Private key.
At the moment I did not find any Public key deriving methods so you definitely need to know the Private and the Public key.
I did not test larger byte arrays because this parameter depends on the memory of the target system (you need the double memory
as the complete data is captured in ciphertextByte and then again in decryptedtextByte).
Edit June 16th 2020: President James K. Polk programmed a method that easily retrieves a public key from a given private key. The source is available in his GitHub-Repo (https://github.com/james-k-polk/McEliece/blob/master/McElieceRecoverPublicFromPrivate.java) and for later convenience shown at the end of this answer. So everyone that has access to a private McEliece key is been able to encrypt data with the retrieved public key! Thanks to President James for his help.
Here are the outputs on the console:
McEliece Fujisaki Pqc Encryption
key generation
PrivateKey length: 4268 algorithm: McEliece-CCA2 format: PKCS#8
PublicKey length: 103429 algorithm: McEliece-CCA2 format: X.509
initialize cipher for encryption
pt length: 52428800 (50 mb)
ct length: 52429056 (50 mb)
initialize cipher for decryption
dt length: 52428800 (50 mb)
compare plaintext <-> decryptedtext: true
class McElieceFujisakiPqcEncryptionLargeData.java
import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
import org.bouncycastle.crypto.InvalidCipherTextException;
import org.bouncycastle.crypto.params.AsymmetricKeyParameter;
import org.bouncycastle.crypto.params.ParametersWithRandom;
import org.bouncycastle.pqc.crypto.mceliece.*;
import org.bouncycastle.pqc.jcajce.provider.BouncyCastlePQCProvider;
import org.bouncycastle.pqc.jcajce.provider.mceliece.BCMcElieceCCA2PrivateKey;
import org.bouncycastle.pqc.jcajce.provider.mceliece.BCMcElieceCCA2PublicKey;
import java.security.*;
import java.util.Arrays;
import java.util.Random;
public class McElieceFujisakiPqcEncryptionLargeData {
public static void main(String[] args) throws InvalidCipherTextException {
System.out.println("McEliece Fujisaki Pqc Encryption");
if (Security.getProvider("BCPQC") == null) {
Security.addProvider(new BouncyCastlePQCProvider());
// used Bouncy Castle: bcprov-jdk15to18-165.jar
}
System.out.println("key generation");
SecureRandom keyRandom = new SecureRandom();
McElieceCCA2Parameters params = new McElieceCCA2Parameters();
McElieceCCA2KeyPairGenerator mcElieceCCA2KeyGen = new McElieceCCA2KeyPairGenerator();
McElieceCCA2KeyGenerationParameters genParam = new McElieceCCA2KeyGenerationParameters(keyRandom, params);
mcElieceCCA2KeyGen.init(genParam);
AsymmetricCipherKeyPair pair = mcElieceCCA2KeyGen.generateKeyPair();
AsymmetricKeyParameter mcEliecePrivateKey = pair.getPrivate();
AsymmetricKeyParameter mcEliecePublicKey = pair.getPublic();
PrivateKey privateKey = new BCMcElieceCCA2PrivateKey((McElieceCCA2PrivateKeyParameters) pair.getPrivate()); // conversion neccessary only for key data
PublicKey publicKey = new BCMcElieceCCA2PublicKey((McElieceCCA2PublicKeyParameters) pair.getPublic()); // conversion neccessary only for key data
System.out.println("PrivateKey length: " + privateKey.getEncoded().length + " algorithm: " + privateKey.getAlgorithm() + " format: " + privateKey.getFormat());
System.out.println("PublicKey length: " + publicKey.getEncoded().length + " algorithm: " + publicKey.getAlgorithm() + " format: " + publicKey.getFormat());
// generate cipher for encryption
System.out.println("\ninitialize cipher for encryption");
ParametersWithRandom param = new ParametersWithRandom(mcEliecePublicKey, keyRandom);
McElieceFujisakiCipher mcElieceFujisakiDigestCipher = new McElieceFujisakiCipher();
mcElieceFujisakiDigestCipher.init(true, param);
// random plaintext
byte[] plaintext = new byte[52428800]; // 50 mb, 50 * 1024 * 1024
new Random().nextBytes(plaintext);
System.out.println("pt length: " + plaintext.length + " (" + (plaintext.length / (1024 * 1024)) + " mb)");
byte[] ciphertext = mcElieceFujisakiDigestCipher.messageEncrypt(plaintext);
System.out.println("ct length: " + ciphertext.length + " (" + (ciphertext.length / (1024 * 1024)) + " mb)");
System.out.println("\ninitialize cipher for decryption");
mcElieceFujisakiDigestCipher.init(false, mcEliecePrivateKey);
byte[] decryptedtext = mcElieceFujisakiDigestCipher.messageDecrypt(ciphertext);
System.out.println("dt length: " + decryptedtext.length + " (" + (decryptedtext.length / (1024 * 1024)) + " mb)");
System.out.println("\ncompare plaintext<-> decryptedtext: " + Arrays.equals(plaintext, decryptedtext));
}
}
Public key Retrieval class by President James K. Polk, available under MIT-Licence:
package com.github.jameskpolk;
import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
import org.bouncycastle.crypto.params.ParametersWithRandom;
import org.bouncycastle.pqc.crypto.mceliece.*;
import org.bouncycastle.pqc.jcajce.provider.mceliece.BCMcElieceCCA2PrivateKey;
import org.bouncycastle.pqc.jcajce.provider.mceliece.BCMcElieceCCA2PublicKey;
import org.bouncycastle.pqc.math.linearalgebra.*;
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
public class McElieceRecoverPublicFromPrivate {
private static final SecureRandom RAND = new SecureRandom();
public static AsymmetricCipherKeyPair generateKeyPair() {
McElieceCCA2KeyPairGenerator kpg = new McElieceCCA2KeyPairGenerator();
McElieceCCA2Parameters params = new McElieceCCA2Parameters();
McElieceCCA2KeyGenerationParameters genParam = new McElieceCCA2KeyGenerationParameters(RAND, params);
kpg.init(genParam);
return kpg.generateKeyPair();
}
public static McElieceCCA2PublicKeyParameters recoverPubFromPriv(McElieceCCA2PrivateKeyParameters priv) {
GF2mField field = priv.getField();
PolynomialGF2mSmallM gp = priv.getGoppaPoly();
GF2Matrix h = GoppaCode.createCanonicalCheckMatrix(field, gp);
Permutation p = priv.getP();
GF2Matrix hp = (GF2Matrix) h.rightMultiply(p);
GF2Matrix sInv = hp.getLeftSubMatrix();
GF2Matrix s = (GF2Matrix) sInv.computeInverse();
GF2Matrix shp = (GF2Matrix)s.rightMultiply(hp);
GF2Matrix m = shp.getRightSubMatrix();
GoppaCode.MaMaPe mmp = new GoppaCode.MaMaPe(sInv, m, p);
GF2Matrix shortH = mmp.getSecondMatrix();
GF2Matrix shortG = (GF2Matrix) shortH.computeTranspose();
// generate public key
return new McElieceCCA2PublicKeyParameters(
priv.getN(), gp.getDegree(), shortG,
priv.getDigest());
}
public static void main(String[] args) throws Exception{
// generate a McEliece key pair
AsymmetricCipherKeyPair bcKeyPair = generateKeyPair();
McElieceCCA2PrivateKeyParameters bcPriv = (McElieceCCA2PrivateKeyParameters) bcKeyPair.getPrivate();
BCMcElieceCCA2PrivateKey priv = new BCMcElieceCCA2PrivateKey(bcPriv);
// get the first public key
McElieceCCA2PublicKeyParameters bcPub1 = (McElieceCCA2PublicKeyParameters) bcKeyPair.getPublic();
BCMcElieceCCA2PublicKey pub1 = new BCMcElieceCCA2PublicKey(bcPub1);
// Now generate a second public key for the private key
McElieceCCA2PublicKeyParameters bcPub2 = recoverPubFromPriv(bcPriv);
BCMcElieceCCA2PublicKey pub2 = new BCMcElieceCCA2PublicKey(bcPub2);
// print some info about sizes
System.out.printf("Size of encrypted messages in bits(bytes): %d(%d)\n",
priv.getEncoded().length, priv.getEncoded().length / 8);
System.out.printf("private key length: %d\n", bcPriv.getK());
System.out.printf("public key1 length: %d\n", pub1.getEncoded().length);
System.out.printf("public key2 length: %d\n", pub2.getEncoded().length);
// now encrypt different messages with each public key.
String message1 = "Deposits should be made to account # 3.1415929";
String message2 = "Deposits should be made to account # 2.71828";
ParametersWithRandom params1 = new ParametersWithRandom(bcPub1, RAND);
ParametersWithRandom params2 = new ParametersWithRandom(bcPub2, RAND);
McElieceFujisakiCipher mcElieceFujisakiDigestCipher1 = new McElieceFujisakiCipher();
McElieceFujisakiCipher mcElieceFujisakiDigestCipher2 = new McElieceFujisakiCipher();
mcElieceFujisakiDigestCipher1.init(true, params1);
mcElieceFujisakiDigestCipher2.init(true, params2);
byte[] ciphertext1 = mcElieceFujisakiDigestCipher1.messageEncrypt(message1.getBytes(StandardCharsets.UTF_8));
byte[] ciphertext2 = mcElieceFujisakiDigestCipher2.messageEncrypt(message2.getBytes(StandardCharsets.UTF_8));
System.out.println("ct1 length: " + ciphertext1.length + " (" + (ciphertext1.length / (1024 * 1024)) + " mb)");
System.out.println("ct2 length: " + ciphertext2.length + " (" + (ciphertext2.length / (1024 * 1024)) + " mb)");
mcElieceFujisakiDigestCipher1.init(false, bcPriv);
mcElieceFujisakiDigestCipher2.init(false, bcPriv);
byte[] decryptedtext1 = mcElieceFujisakiDigestCipher1.messageDecrypt(ciphertext1);
byte[] decryptedtext2 = mcElieceFujisakiDigestCipher2.messageDecrypt(ciphertext2);
System.out.printf("Decrypted message 1: %s\n", new String(decryptedtext1, StandardCharsets.UTF_8));
System.out.printf("Decrypted message 2: %s\n", new String(decryptedtext2, StandardCharsets.UTF_8));
}
}
I know, that standard RSA supports just a small data blocks to encrypt,
That's why we use a hybrid cryptosystem. Data are encrypted using a symmetric cipher (data key), and the symmetric data key is encrypted using an asymmetric cipher.
I do not need changes detection, I need to make changes impossible (ok, as hard as possible).
If you are unable to enforce any read-only input/filesystem, then detecting changes is the best you can do. Either it's failed decryption or signature.
Actually to ensure data integrity I'd really use signing, not pure encryption. I see you don't want that, but at the end it will there. Some ciphers / cipher modes are malleable - data can be changed even when encrypted and without any authentication (mac, signature) the decryption is valid and you won't be able to detect the integrity failure.
If you would just rely on application to detect that data are corrupted after failed decryption, you are creating a perfect decryption oracle (breaking security)
I suppose, that the easiest way to do that, is to encrypt data with some secret key and to include the public key in the app, so app could decrypt and show the data, but without the secret key users would not be able to change the content.
Anything hardcoded in your app you can consider as revealed/public. You correctly identified the risk. If you have a dedicated user, nothing prevents the user to change the key in the app and pass invalid data. So - for anything that runs at the client, you can make the integrity stronger, but not perfect. At the end - you have to make some assumptions about adversary's abilities.
I encrypt data with my private key, pass to app, app in runtime decrypts data with it's public key and
In theory (mathematically) you can do that, but most of the current libraries will not let you use the key pairs wrong way (private key is intended for decryption or signing, public for encryption or validation). If you want to code such a solution yourself, you are in risk of creating weaknesses you may not be aware of (proper padding, timing,..)
I believe there are even some weaknesses in the scheme (encrypting using the private key), but I cannot recall details, there are people with deeper knowledge in the topic (e. g. James Polk from comments)
Edit:
Examples to create a signature or MAC : https://docs.oracle.com/javase/7/docs/technotes/guides/security/crypto/CryptoSpec.html
btw - using aes-gcm the Java Cipher implementation automatically appends the mac tag to the end of the ciphertext
An interesting question. As far as I can see from your question, the data itself is not secret. Your problem is that the user should not be able to change the data (or that you should be able to detect that he or she has changed it). In this case, a hash function (including possibly a cryptographic hash function) might well be a better approach. See https://en.wikipedia.org/wiki/Hash_function and https://en.wikipedia.org/wiki/Cryptographic_hash_function. If you use a hash function, then you can always detect whether the user has tried to change the data.
The cryptographic hash functions are one-way, so that you do not need to store any key in your program.
I am getting information back from Visa Checkout in an encrypted format. The guide on their site provides these instructions:
First, you must decrypt the dynamic key (encKey), then use the decrypted dynamic key value to decrypt the payment data payload (encPaymentData).
Follow these four steps to decrypt the encKey:
Base64-decode the encKey.
Remove the first 32 bytes of the decoded value. This is the HMAC (Hash Message Authentication Code). Calculate a SHA-256 HMAC of the
rest of the decoded data using your API Shared Secret and compare it
to the HMAC from the first 32 bytes.
The next 16 bytes should be removed and used as the IV (Initialization Vector) for the decryption algorithm.
Decrypt the remaining data using AES-256-CBC, the IV from step 3, and the SHA-256 hash of your API Shared Secret.
Follow these four steps to decrypt the encPaymentData using the
decrypted encKey:
Base64-decode the encPaymentData.
Remove the first 32 bytes of the decoded value. This is the HMAC. Calculate a SHA-256 HMAC of the rest of the decoded data using
the
decrypted encKey and compare it with the HMAC from the first 32
bytes.
The next 16 bytes should be removed and used as the IV for the decryption algorithm.
Decrypt the rest of the encPaymentData payload using AES-256-CBC, the IV from step 3, and the SHA256-hash of the
decrypted encKey.
I tried using ColdFusion but I am lost somewhat with the encryption issues, and am unable to fix the code. Below I have what is required. I am stuck on the step 3 & 4 where they say compare it and then decrypt it. Can someone guide what could be done to fix it?
enckey:
2M2WWOD4wANsGwWTmPqQIQYdz9WPwgiR0ntJHGaxm23b5a1sWUtCBcUQUMMtc9hvcYavJ6yqPgETOGZgDOdd9qjDwIb2aV9DLZT1iIcB3zNN5Ddhxd9iiui6TAlJxU/O
encPaymentData:
X2TXp0ZmwHrtfzSP5TPjUOjdZb0rjsHeDSqr8TwIF/VR8sMQhWN5hP4IRhQxWT CZcQhxZoUHP 0g/E/ot sjREAJ8YQf7c7jSzKsXRH/wrew5rQit2wJBlVSSZ YoLeIHbLrTz CfIoFv09hixl7ff27u0YCyV0zjP5vNfCBEIwfqyriqwXK2J QEOxQiKzDUW4br3o1t31aymCQC9eBBpoVKjFfSKlNXM9QEdNZBcLMZ8Wlv8lF/ua bnwshbM9u7Uhudqvut94RZEW NzkRD8MfBo12e/XhnL35qxGpHeQNPClC4EQDK6U/HmegeOj BZLbIIYBs6t9E8Q3AKBwfiPOFgB gSVnhXKnd3nKvllaG BaGrQJtk 7QAtnHMHxQAO5rdiS9465HCdiHa8zlv7SkvWh8EwcKCiT4qiZSM6QuYAeRSzDpPS1gsZ54Q9LizUnueH7yyzSd47cLPd0VlOQxobKtNN2LrsRb3IwOfzuwGnSRf2cNp49hBmmGP1b0BC hhB6UpCqP2ixTPvui NwMYzqZUe336bF1mfnKzEbEZIvIrPyx3uMiLDAns2g7S80gMNnHb/09i49xbfY3V7oudeiHV99FCh67DuG3uHE3/HzIZbcnxJwVJoJj6/3DuzK/Kw1JqSorE0M1qxUqoNkJC4aNCBrqfTlR7/eErrvB554TUZwcyQXqKCwrKv4NJEw6S0n3W1VASkfA0atbJQX2aLgx9kqnhYdDbaU8UcFIoeA45 yEuQ9vXzo2ILQhvamsAAFQd3i4mEOZ KNtMu25dDFlORn5C/oTZ1t1dzJoYMvq44gejp6L3IK e7JCugGchr963a2kd8NFa3wctRDHF8ChHxawVlU0aY7nasrVireMFLiM 9XIb4abfDtct/j1Q8IGN0hRkgCHO6dlnOrAaaQDYYH3axaMDp5Onb04whULYqGbn/XSR8Sn8gnoFbYqVJbR5aCp5Pe9TpfwxlEvV3z8ZfsASqW2y So9gpwg2y16K/FX3Io6kqeqUlAxbTRDfN/ofDIJaO H PUu2teqjvwvCkjPciGOQdXT5JxqoCiHqRwD0zeZPcG3b9Nfrq3Caf6zjwhf /CMeGc3dNHhSkXox R50MP8BlLWk/bXZRScTE/HSrVxE n073utHAnbVOM3gVha0Hr8TmoV8z1vBg5fY253so6kQX61ZIfHneCAZo0qeKRgDgLUryVPmUNc5 yKP8DxtmHl/0YUztpgyEx5njsrn1L 3EHMMUhui8d LQdNZoEpZ9U1Xb7XVsV5gnwR/mOITNOKJQsine4zMMHBcomHclrM0CuI58YrKPqworCmK6CYfzSc8UmXxXUe5dzND/DS9XgqDttQic2/OqTSAK63ynnrNqzr3D56VpDBeDeQjk3mc/0zmuFAPEXoAQoQKfD6HEuajvWJebQ6QIPgA TshqsnPlktbpftr4lsuB1tHS/W8D7SYVFMC/Kxy9QuYWs0cmRTtzfWEKIRHeDElOTQCX5JB5PgzVhhi5kYTi488Ba8j4zvNUw55hEoMxONYO7eMjJosmNjULsT492LGw3EfAgmgx9h3yFLQRZgfylg0h4PfLlcPOAdsnVX9/yLElD xu7Atwc4S7pBWTHvwue7PpRvWpTeqkU5sqiX4KcV5x8rk mBtxm48a8fsmp GNf 4IjwXu9cQaU9WLipiEnkqFsYo7/aAsmmKWBETyQg9BFXYK 165vrzSX8WTsv6ZZDnVjcE1n4Ov8Jl2cnAigoQbB0ROPpIRzZ3zH2diUv1vzlSuh9gbEJf3uQRKlYRVUbpboC0RbQ/7jgznfJAWyLykyDQ0EB8fVEOtbP1l4JEz39QwAU18ph3btnWWuKEV4 ghYvNG4m1DYntSF57s2ajRS6rPtR oYvGjrJL9zbHBhKHlfkIPC0TKotOCi96mqpikbBEfIZSomHxYgDwYCSvt60zaDIjlBxZ1UBdK JL0554Wia9W3Wg91bmYS9Q4SXMT8r4xGYB7OutEV24n7p088rVm/w2SZSiqlLqai539k6WGkzEQf19ytPtIE81a N z7aijTjy 7FCuVPF90svI5/NoGpSINqv84HUcMU71BvXUIT53Ea6CCpiWvvOPpo/XZar44emlIG0UgeB kfP6C6sis=
Secret code:
zRf7WZ3nM7ON{U0E6J5S}KpVm#k2ReDyq#1lG9go
CF Code:
<cfset str = "2M2WWOD4wANsGwWTmPqQIQYdz9WPwgiR0ntJHGaxm23b5a1sWUtCBcUQUMMtc9hvcYavJ6yqPgETOGZgDOdd9qjDwIb2aV9DLZT1iIcB3zNN5Ddhxd9iiui6TAlJxU/O">
<cfset tobas = tobase64(str)>
<cfset getFirst32bytes = Left(tobas,32)>
<cfset tobas2 = RemoveChars(tobas,1,32)>
<cfdump var="#tobas2#">
<cfset key = "zRf7WZ3nM7ON{U0E6J5S}KpVm#k2ReDyq##1lG9go">
<cfset x = hmac("#tobas2#","#key#","HMACSHA256")>
<cfset y = hmac("#getFirst32bytes#","#key#","HMACSHA256")>
<cfset decalgo = Left(x,16)>
<cfset decremainingData = RemoveChars(x,1,16)>
<cfset getDec = Decrypt(decalgo,"#key#","AES")>
<cfdump var="#x#"><br>
<cfdump var="#y#"><br>
<cfdump var="#decalgo#">
<cfdump var="#decremainingData#">
<cfdump var="#getDec#">
This is the java example they have on their site:
private static final String CIPHER_ALGORITHM = "AES/CBC/PKCS5Padding";
private static final String HASH_ALGORITHM = "SHA-256";
private static final String HMAC_ALGORITHM = "HmacSHA256";
private static final int IV_LENGTH = 16, HMAC_LENGTH = 32;
private static final Charset utf8 = Charset.forName("UTF-8");
private static final Provider bcProvider;
static {
bcProvider = new BouncyCastleProvider();
if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) {
Security.addProvider(bcProvider);
}
}
private static byte[] decrypt(byte[] key, byte[] data) throws GeneralSecurityException {
byte[] decodedData = Base64.decode(data);
if (decodedData == null || decodedData.length <= IV_LENGTH) {
throw new RuntimeException("Bad input data.");
}
byte[] hmac = new byte[HMAC_LENGTH];
System.arraycopy(decodedData, 0, hmac, 0, HMAC_LENGTH);
if (!Arrays.equals(hmac,
hmac(key, decodedData, HMAC_LENGTH, decodedData.length– HMAC_LENGTH))) {
throw new RuntimeException("HMAC validation failed.");
}
byte[] iv = new byte[IV_LENGTH];
System.arraycopy(decodedData, HMAC_LENGTH, iv, 0, IV_LENGTH);
Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM, bcProvider);
cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(hash(key), "AES"),
new IvParameterSpec(iv));
return cipher.doFinal(decodedData, HMAC_LENGTH + IV_LENGTH,
decodedData.length– HMAC_LENGTH– IV_LENGTH);
}
private static byte[] hash(byte[] key) throws NoSuchAlgorithmException {
MessageDigest md = MessageDigest.getInstance(HASH_ALGORITHM);
md.update(key);
return md.digest();
}
private static byte[] hmac(byte[] key, byte[] data, int offset, int length)
throws GeneralSecurityException {
Mac mac = Mac.getInstance(HMAC_ALGORITHM, bcProvider);
mac.init(new SecretKeySpec(key, HMAC_ALGORITHM));
mac.update(data, offset, length);
return mac.doFinal();
}
An important thing to understand about the sample code is that it refers to bytes. Your CF code is using characters. That might seem like a trivial distinction, but they are totally different things, which will produce very, very different results. In order to decrypt successfully, you need to work with the bytes (or binary) of the given strings - not characters.
Although it is possible to manipulate binary arrays using core CF functions, like arraySlice(), the syntax gets a little bulky/clunky at times. The reason is that binary arrays are a different type of object than your standard CF array, i.e. byte[] versus java.util.List. So depending on which functions are used, you may need javacast to coerce variables into the expected type. With that in mind ..
Part I - Decrypt the encKey
Base64-decode the encKey.
Remove the first 32 bytes of the decoded value. This is the HMAC (Hash Message Authentication Code). Calculate a SHA-256 HMAC of the
rest of the decoded data using your API Shared Secret and compare it
to the HMAC from the first 32 bytes.
First convert the base64 string into binary using binaryDecode. Then extract the appropriate number of bytes from the returned array. This is the expected HMAC value:
hmacSize = 32;
binaryToDecrypt = binaryDecode(encryptedKey, "base64");
expectedHMAC = binaryEncode( javacast("byte[]", arraySlice(binaryToDecrypt, 1, hmacSize))
, "hex" );
Next, extract all of the remaining bytes, and use them to calculate the actual HMAC. Verify it against the expected value. If the two do not match, something went wrong.
remainData = arraySlice(binaryToDecrypt, hmacSize + 1);
actualHMAC = hmac( javacast("byte[]", remainData ), sharedSecret, "HMACSHA256");
if (compare(actualHMAC, expectedHMAC) != 0) {
throw("ERROR: Invalid HMAC ["& actualHMAC &"]. Expected ["& expectedHMAC &"]");
}
The next 16 bytes should be removed and used as the IV (Initialization Vector) for the decryption algorithm.
The remaining bytes contains an IV, followed by the encrypted value. Before you can decrypt the latter, you need to extract and separate the two:
ivSize = 16;
ivValue = javacast("byte[]", arraySlice(remainData, 1, ivSize));
encryptedValue = javacast("byte[]", arraySlice(remainData, ivSize + 1));
Decrypt the remaining data using AES-256-CBC, the IV from step 3, and the SHA-256 hash of your API Shared Secret.
The last step before you can decrypt is to generate the decryption key, by hashing the shared secret. Unfortunately, CF's hash() function always returns a hex string. So it must be converted into base64 format to be compatible with the decryption function.
keyHex = hash(sharedSecret, "SHA-256", "utf-8");
keyBase64 = binaryEncode(binaryDecode(keyHex, "hex"), "base64");
Finally, use all three values to decrypt. The returned binary will contain the encryption key used in part II.
decryptedKeyBinary = decryptBinary( encryptedValue
, keyBase64
, "AES/CBC/PKCS5Padding"
, ivValue);
Part II - Decrypt the encPaymentData
Use the exact same process as in Part I, just swap the variables:
Use encPaymentData instead of encryptedKey
Use decryptedKeyBinary instead of sharedSecret.
The final, decrypted result will be binary. Use charsetEncode to convert it back into a human readable string:
result = charsetEncode(decryptedResult, "utf-8");
NB: The sample values you posted appear to be broken, as they do not even work with the java example. The steps above do produce the correct result when used valid values (key, data, etcetera).
In my Android app I have a SHA256 hash which I must further hash with the RIPEMD160 message digest algorithm.
I can output the correct sha256 and ripemd160 hash of any string, but when I try to hash the sha256 hash with ripemd160 I get a hash which is incorrect.
According to online hash calculators, the SHA256 value of the string 'test'(all lowercase) is:
9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08
And the RIPEMD160 value of the string 'test' is:
5e52fee47e6b070565f74372468cdc699de89107
The value from hashing the resulting sha256 hash with ripemd160 according to online calcs is:
4efc1c36d3349189fb3486d2914f56e05d3e66f8
And the one my app gives me is:
cebaa98c19807134434d107b0d3e5692a516ea66
which is obviously wrong.
Here is my code:
public static String toRIPEMD160(String in)
{
byte[] addr = in.getBytes();
byte[] out = new byte[20];
RIPEMD160Digest digest = new RIPEMD160Digest();
byte[] sha256 = sha256(addr);
digest.update(sha256,0,sha256.length);
digest.doFinal(out,0);
return getHexString(out);
}
public static byte[] sha256(byte[] data)
{
byte[] sha256 = new byte[32];
try
{
sha256 = MessageDigest.getInstance("SHA-256").digest(data);
}
catch(NoSuchAlgorithmException e)
{}
return sha256;
}
For the ripemd160 algorithm, you need bouncycastle and java.security.MessageDigest for sha256.
Your "online calculator" result is the result of hashing the bytes of the string "test" with SHA-256, converting the result of that hash to a hex string, then taking the bytes corresponding to the ASCII characters of that hex string and hashing those a second time. This is very different from your Java code, which passes the bytes that come out of the first hash directly to the second one, without printing them as hex and turning those characters back into bytes in between. The single byte with value 254 (decimal) becomes "fe" in hex, which becomes the two-byte sequence [0x66, 0x65] when converted back to bytes.
Your hash is working fine. The problem is that the online calculators that you're using are treating your input:
9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08
as a string instead of an array of bytes. In other words, it's treating each character as a byte instead of parsing character pairs as bytes in hexadecimal. If I give this as a string to online calculators, I indeed get exactly what you got:
4efc1c36d3349189fb3486d2914f56e05d3e66f8
However, you're treating the output as an array of bytes instead of a String and that's giving you different results. You should encode your raw SHA256 hash as a string, then pass the encoded string to the hash function. I see you have a getHexString method, so we'll just use that.
public static String toRIPEMD160(String in) {
try {
byte[] addr = in.getBytes();
byte[] out = new byte[20];
RIPEMD160Digest digest = new RIPEMD160Digest();
// These are the lines that changed
byte[] rawSha256 = sha256(addr);
String encodedSha256 = getHexString(rawSha256);
byte[] strBytes = encodedSha256.getBytes("UTF-8");
digest.update(strBytes, 0, strBytes.length);
digest.doFinal(out, 0);
return getHexString(out);
} catch (UnsupportedEncodingException ex) {
// Never happens, everything supports UTF-8
return null;
}
}
If you want to know it's working, take the value of encodedSha256 and put that into an online hash calculator. As long as the calculator uses UTF-8 encoding to turn the string into a byte array, it will match your output.
To get printable version of byte[] digest use this code:
StringBuffer hexString = new StringBuffer();
for (int i=0;i<out.length;i++) {
hexString.append( String.format("%02x", 0xFF & out[i]) );
}
and then call hexString.toString();
I'm trying to make a simple String to SHA1 converter in Java and this is what I've got...
public static String toSHA1(byte[] convertme) {
MessageDigest md = null;
try {
md = MessageDigest.getInstance("SHA-1");
}
catch(NoSuchAlgorithmException e) {
e.printStackTrace();
}
return new String(md.digest(convertme));
}
When I pass it toSHA1("password".getBytes()), I get [�a�ɹ??�%l�3~��. I know it's probably a simple encoding fix like UTF-8, but could someone tell me what I should do to get what I want which is 5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8? Or am I doing this completely wrong?
UPDATE
You can use Apache Commons Codec (version 1.7+) to do this job for you.
DigestUtils.sha1Hex(stringToConvertToSHexRepresentation)
Thanks to #Jon Onstott for this suggestion.
Old Answer
Convert your Byte Array to Hex String. Real's How To tells you how.
return byteArrayToHexString(md.digest(convertme))
and (copied from Real's How To)
public static String byteArrayToHexString(byte[] b) {
String result = "";
for (int i=0; i < b.length; i++) {
result +=
Integer.toString( ( b[i] & 0xff ) + 0x100, 16).substring( 1 );
}
return result;
}
BTW, you may get more compact representation using Base64. Apache Commons Codec API 1.4, has this nice utility to take away all the pain. refer here
This is my solution of converting string to sha1. It works well in my Android app:
private static String encryptPassword(String password)
{
String sha1 = "";
try
{
MessageDigest crypt = MessageDigest.getInstance("SHA-1");
crypt.reset();
crypt.update(password.getBytes("UTF-8"));
sha1 = byteToHex(crypt.digest());
}
catch(NoSuchAlgorithmException e)
{
e.printStackTrace();
}
catch(UnsupportedEncodingException e)
{
e.printStackTrace();
}
return sha1;
}
private static String byteToHex(final byte[] hash)
{
Formatter formatter = new Formatter();
for (byte b : hash)
{
formatter.format("%02x", b);
}
String result = formatter.toString();
formatter.close();
return result;
}
Using Guava Hashing class:
Hashing.sha1().hashString( "password", Charsets.UTF_8 ).toString()
SHA-1 (and all other hashing algorithms) return binary data. That means that (in Java) they produce a byte[]. That byte array does not represent any specific characters, which means you can't simply turn it into a String like you did.
If you need a String, then you have to format that byte[] in a way that can be represented as a String (otherwise, just keep the byte[] around).
Two common ways of representing arbitrary byte[] as printable characters are BASE64 or simple hex-Strings (i.e. representing each byte by two hexadecimal digits). It looks like you're trying to produce a hex-String.
There's also another pitfall: if you want to get the SHA-1 of a Java String, then you need to convert that String to a byte[] first (as the input of SHA-1 is a byte[] as well). If you simply use myString.getBytes() as you showed, then it will use the platform default encoding and as such will be dependent on the environment you run it in (for example it could return different data based on the language/locale setting of your OS).
A better solution is to specify the encoding to use for the String-to-byte[] conversion like this: myString.getBytes("UTF-8"). Choosing UTF-8 (or another encoding that can represent every Unicode character) is the safest choice here.
This is a simple solution that can be used when converting a string to a hex format:
private static String encryptPassword(String password) throws NoSuchAlgorithmException, UnsupportedEncodingException {
MessageDigest crypt = MessageDigest.getInstance("SHA-1");
crypt.reset();
crypt.update(password.getBytes("UTF-8"));
return new BigInteger(1, crypt.digest()).toString(16);
}
Just use the apache commons codec library. They have a utility class called DigestUtils
No need to get into details.
As mentioned before use apache commons codec. It's recommended by Spring guys as well (see DigestUtils in Spring doc). E.g.:
DigestUtils.sha1Hex(b);
Definitely wouldn't use the top rated answer here.
It is not printing correctly because you need to use Base64 encoding. With Java 8 you can encode using Base64 encoder class.
public static String toSHA1(byte[] convertme) throws NoSuchAlgorithmException {
MessageDigest md = MessageDigest.getInstance("SHA-1");
return Base64.getEncoder().encodeToString(md.digest(convertme));
}
Result
This will give you your expected output of 5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8
Message Digest (hash) is byte[] in byte[] out
A message digest is defined as a function that takes a raw byte array and returns a raw byte array (aka byte[]). For example SHA-1 (Secure Hash Algorithm 1) has a digest size of 160 bit or 20 byte. Raw byte arrays cannot usually be interpreted as character encodings like UTF-8, because not every byte in every order is an legal that encoding. So converting them to a String with:
new String(md.digest(subject), StandardCharsets.UTF_8)
might create some illegal sequences or has code-pointers to undefined Unicode mappings:
[�a�ɹ??�%l�3~��.
Binary-to-text Encoding
For that binary-to-text encoding is used. With hashes, the one that is used most is the HEX encoding or Base16. Basically a byte can have the value from 0 to 255 (or -128 to 127 signed) which is equivalent to the HEX representation of 0x00-0xFF. Therefore hex will double the required length of the output, that means a 20 byte output will create a 40 character long hex string, e.g.:
2fd4e1c67a2d28fced849ee1bb76e7391b93eb12
Note that it is not required to use hex encoding. You could also use something like base64. Hex is often preferred because it is easier readable by humans and has a defined output length without the need for padding.
You can convert a byte array to hex with JDK functionality alone:
new BigInteger(1, token).toString(16)
Note however that BigInteger will interpret given byte array as number and not as a byte string. That means leading zeros will not be outputted and the resulting string may be shorter than 40 chars.
Using Libraries to Encode to HEX
You could now copy and paste an untested byte-to-hex method from Stack Overflow or use massive dependencies like Guava.
To have a go-to solution for most byte related issues I implemented a utility to handle these cases: bytes-java (Github)
To convert your message digest byte array you could just do
String hex = Bytes.wrap(md.digest(subject)).encodeHex();
or you could just use the built-in hash feature
String hex = Bytes.from(subject).hashSha1().encodeHex();
Base 64 Representation of SHA1 Hash:
String hashedVal = Base64.getEncoder().encodeToString(DigestUtils.sha1(stringValue.getBytes(Charset.forName("UTF-8"))));
Convert byte array to hex string.
public static String toSHA1(byte[] convertme) {
final char[] HEX_CHARS = "0123456789ABCDEF".toCharArray();
MessageDigest md = null;
try {
md = MessageDigest.getInstance("SHA-1");
}
catch(NoSuchAlgorithmException e) {
e.printStackTrace();
}
byte[] buf = md.digest(convertme);
char[] chars = new char[2 * buf.length];
for (int i = 0; i < buf.length; ++i) {
chars[2 * i] = HEX_CHARS[(buf[i] & 0xF0) >>> 4];
chars[2 * i + 1] = HEX_CHARS[buf[i] & 0x0F];
}
return new String(chars);
}
The reason this doesn't work is that when you call String(md.digest(convertme)), you are telling Java to interpret a sequence of encrypted bytes as a String. What you want is to convert the bytes into hexadecimal characters.
Maybe this helps (works on java 17):
import org.apache.tomcat.util.codec.binary.Base64;
return new String(Base64.encodeBase64(md.digest(convertme)));