I am generating HMAC-SHA256 in Java. If the key is set to "" then The following exception is thrown:
java.lang.IllegalArgumentException: Empty key
But I can generate the HMAC using empty key in JavaScript using the
CryptoJS.HmacSHA256("Sample Text", "") method in CryptoJS. In Java a space is also accepted as key but empty key is not accepted.
Is it possible to use empty key in Java?
Java - as most programming languages - is Turing complete, so you can do any calculation that another language can do, even if you have to program it yourself.
Unfortunately the exception you've shown is part of SecretKeySpec, which means that you cannot generate the key directly. You cannot just use a different provider either because the key creation would be the same.
However, if you're smart then you'll see that SecretKey is just an interface, which means you can implement it:
public static class EmptyKey implements SecretKey {
private static final long serialVersionUID = 1L;
#Override
public String getAlgorithm() {
return "HMAC";
}
#Override
public String getFormat() {
return "RAW";
}
#Override
public byte[] getEncoded() {
// return empty key data
return new byte[0];
}
}
now apparently the HMAC implementation of Java doesn't disallow empty keys itself, so this will work. Of course it might not work in different implementations or versions of Java, although inserting a check in HMAC doesn't seem likely as it could be a valid use case of HMAC.
Alternatively you can of course use another library that implements HMAC, such as Bouncy Castle, and sidestep the JCA altogether.
HMac hmac = new HMac(new SHA256Digest());
hmac.init(new KeyParameter(new byte[0]));
byte[] out = new byte[hmac.getMacSize()];
// you need to insert the message here, I'm using an empty message
hmac.doFinal(out, 0);
Of course, if your code requires a Java Mac object then this is not the solution for you.
The code in both options in this answer produce the same results, so you can be pretty sure that it is valid.
Related
The plain text is signed using java.security.Signature. Below is the code used to sign the plain text
public String getSignature(String plainText) throws Exception
{
KeyStore keyStore = loadKeyStore(); // A local method to read the keystore file from file system.
PrivateKey privateKey = (PrivateKey) keyStore.getKey(KEY_ALIAS_IN_KEYSTORE, KEYSTORE_PASSWORD.toCharArray());
Signature privateSignature = Signature.getInstance(SIGNATUREALGO);
privateSignature.initSign(privateKey);
privateSignature.update(plainText.getBytes("UTF-8"));
byte[] signature = privateSignature.sign();
return String.valueOf(signature);
// KEY_ALIAS_IN_KEYSTORE, KEYSTORE_PASSWORD and SIGNATUREALGO are all constant Strings
}
Note 1: I found online a way to verify the signature using the public key Java Code Examples for java.security.Signature#verify(). But this is not what I require.
Note 2: I also found a ways to encrypt and decrypt as mentioned here RSA Signing and Encryption in Java. But the use case I have in hand is to get the original plain text from a signed data. Is that possible?
No, you can't retrieve the original content from just the signature.
The signature alone does not contain enough information to restore the original clear text, no matter what keys you have access to.
The basic idea of a signature is to send it together with the clear text. That means the clear text will be visible, but the signature can be used to verify that the message was written (or at least signed) by who claims to have done so and has not been tampered with since then.
Signing something is different from encrypting it. The two often uses the same or related technologies and both fall under cryptography.
The code generate the public key is
public static final String CHARSET = "UTF-8";
public static final String RSA_ALGORITHM = "RSA";
public static Map<String, String> createKeys(int keySize){
KeyPairGenerator kpg;
try{
kpg = KeyPairGenerator.getInstance(RSA_ALGORITHM);
}catch(NoSuchAlgorithmException e){
throw new IllegalArgumentException("No such algorithm-->[" + RSA_ALGORITHM + "]");
}
kpg.initialize(keySize);
KeyPair keyPair = kpg.generateKeyPair();
Key publicKey = keyPair.getPublic();
String publicKeyStr = Base64.encodeBase64URLSafeString(publicKey.getEncoded());
Key privateKey = keyPair.getPrivate();
String privateKeyStr = Base64.encodeBase64URLSafeString(privateKey.getEncoded());
Map<String, String> keyPairMap = new HashMap<String, String>();
keyPairMap.put("publicKey", publicKeyStr);
keyPairMap.put("privateKey", privateKeyStr);
return keyPairMap;
}
public static RSAPublicKey getPublicKey(String publicKey) throws NoSuchAlgorithmException, InvalidKeySpecException {
KeyFactory keyFactory = KeyFactory.getInstance(RSA_ALGORITHM);
X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(Base64.decodeBase64(publicKey));
RSAPublicKey key = (RSAPublicKey) keyFactory.generatePublic(x509KeySpec);
return key;
}
The key is then send to me, and I want to read it with python importKey(). But I always get the error "RSA key format is not supported".
The key is "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDGrLFBqubzi45M_yxs5Ps4XW3DIOeAo5x7Ca9EYmWAig3Rb3Efm2PCgipwNube2Ae5eUI5dYlQW32FSF81rw7vNdwfODDzITyWRPLEuVbBbkF5zD6kTxycqlVbH-uTyb95181jpY_XY6tmEOCZCq3mZhil9VA4ZvAoSBcJ8muXaQIDAQAB"
After searching with Google, I've try to add header "-----BEGIN RSA PUBLIC KEY-----" to it and nothing different.
You are using a 'URLsafe' base64 encoder, although not the standard Java one. 'URLsafe' uses different characters (for code values 62 and 63) and no padding, and no linebreaks. PEM format (which was designed long before URLsafe encoding existed, indeed over a year before URLs existed!) uses the traditional base64 characters (now associated mostly with MIME), with padding, and with linebreaks (every 64 characters). Although not all software checks for linebreaks; you don't say which Python crypto library you are using (there are several) so I can't check if it cares about this point.
In Java 8 up you can use Base64.getMimeEncoder() to handle most of these, but if you are stuck on older Java (see below) and/or some other library, you'll have to give details about it. You could convert the - _ characters to + /, add = padding to a multiple of 4, and add linebreaks if and as needed.
OTOH the Python libs I've looked at accept 'DER' (i.e. binary) as well as PEM, so you could just decode the base64 (many decoders can handle lack of padding and at least some can handle both charsets with or without specification) and use that as-is.
The publickey encoding used by Java (which it imprecisely calls "X.509") and also by OpenSSL and some other things is generic and includes an algorithm identifier, so the correct PEM labels are -----BEGIN PUBLIC KEY----- and ------END PUBLIC KEY----- (NO RSA).
You don't say what Java you use, but it apparently defaulted to 1024-bit for RSA, which is obsolete for several years and is no longer considered to provide adequate safety margin (although there are no open reports yet of actually breaking it). 2048 is now widely considered the minimum, and some applications or environments for various reasons use more. But deciding what crypto parameters to use (and indeed whether your application should even be using RSA, and if so which variant) are not programming questions and offtopic for SO; they belong on either crypto.SX (for the underlying principles) or security.SX (for applications).
I'm attempting to encrypt data using KMS and the AWS Encryption SDK. Looking at the example provided in the AWS documentation, it appears that there is nowhere to explicitly set the data key.
I've found API documentation for the EncryptionMaterialsRequest class that allows you to set the plaintext key using the associated builder class, EncryptionMaterialsRequest.Builder, and this class has a method that returns an instance of EncryptionMaterials. I can't find anywhere to use the EncryptionMaterials instance when executing the encrypt operation.
Here is the code I have so far. Note that the EncryptionMaterials instance isn't used in the request.
public static void encryptData(String dataToEncrypt, String keyID) {
final KmsMasterKeyProvider prov = new KmsMasterKeyProvider(keyID);
DefaultCryptoMaterialsManager manager = new DefaultCryptoMaterialsManager(prov);
byte[] plaintextKey = generateDataKey(keyID);
EncryptionMaterialsRequest request = EncryptionMaterialsRequest
.newBuilder()
.setPlaintext(plaintextKey)
.build();
EncryptionMaterials materials = manager.getMaterialsForEncrypt(request);
AwsCrypto crypto = new AwsCrypto();
String encryptedString = crypto.encryptString(manager, dataToEncrypt).getResult();
}
public byte[] generateDataKey(String keyID) {
GenerateDataKeyRequest dataKeyRequest = new GenerateDataKeyRequest();
dataKeyRequest.setKeyId(keyID);
dataKeyRequest.setKeySpec(DataKeySpec.AES_256);
GenerateDataKeyResult dataKeyResult = kmsClient.generateDataKey(dataKeyRequest);
ByteBuffer encryptedKey = dataKeyResult.getCiphertextBlob();
byte[] arr = new byte[encryptedKey.remaining()];
encryptedKey.get(arr);
return arr;
}
What is the recommended approach encrypting data using the AWS Encryption SDK with a data key generated by KMS?
#Viccari is correct, but it sounds like some context around the intended use of these constructs would help explain why.
Unless you are building a custom cryptographic materials manager you should not be creating EncryptionMaterials; the client and management components take care of that for you.
The client asks the cryptographic materials manager for encryption materials on every encrypt call. Depending on the cryptographic materials manager, what exactly happens next might be different.
In the case of the DefaulCryptoMaterialsManager, it then asks the provided master key provider for all of the master keys to use, then uses those master keys to generate and encrypt the data key (one is used to generate and encrypt, any additional are used to encrypt).
In the case of the CachingCryptoMaterialsManager, it adds a caching layer between the client and another cryptographic materials manager.
If you want to use the AWS Encryption SDK with AWS KMS, the recommended approach is to simply provide an instance of KmsMasterKey or KmsMasterKeyProvider, or a cryptographic materials manager that ultimately uses one of those, in the encrypt call. All of the details are taken care of by the client.
If you're interested in more details about how these concepts fit together, our concepts documentation[1] would be a good place to start.
[1] https://docs.aws.amazon.com/encryption-sdk/latest/developer-guide/concepts.html
My question for you would be: why not using the DefaultCryptoMaterialsManager, which should generate a new data key from the master key for each encryption operation? Why are you going to reuse the data keys? This does not sound like a sound approach from the security perspective.
But if you want to do that, you need to provide an implementation of the CryptoMaterialsManager interface.
Instead of using DefaultCryptoMaterialsManager, create a new class, let's say, MyCryptoMaterialsManager, that implements the interface above.
Something like this would do it:
public static void encryptData(String dataToEncrypt, String keyID) {
// not sure whether you need this or where you're getting the data key from.
final KmsMasterKeyProvider prov = new KmsMasterKeyProvider(keyID);
MyCryptoMaterialsManager manager = new MyCryptoMaterialsManager(prov);
byte[] plaintextKey = generateDataKey(keyID);
EncryptionMaterialsRequest request = EncryptionMaterialsRequest
.newBuilder()
.setPlaintext(plaintextKey)
.build();
// this, you told you know how to do:
EncryptionMaterials materials = manager.getMaterialsForEncrypt(request);
AwsCrypto crypto = new AwsCrypto();
String encryptedString = crypto.encryptString(manager, dataToEncrypt).getResult();
}
public byte[] generateDataKey(String keyID) {
GenerateDataKeyRequest dataKeyRequest = new GenerateDataKeyRequest();
dataKeyRequest.setKeyId(keyID);
dataKeyRequest.setKeySpec(DataKeySpec.AES_256);
GenerateDataKeyResult dataKeyResult = kmsClient.generateDataKey(dataKeyRequest);
ByteBuffer encryptedKey = dataKeyResult.getCiphertextBlob();
byte[] arr = new byte[encryptedKey.remaining()];
encryptedKey.get(arr);
return arr;
}
If cost or number of calls to KMS is a concern, you could also use the CachingCryptoMaterialsManager instead. It provides guarantees like making sure that a data key is not used an indefinite number of times.
I am trying to duplicate an encryption process that is working in Java over to iOS/OSX.
My Java code is as follows:
PublicKey publicKey = KeyFactory.getInstance("RSA").
generatePublic(new RSAPublicKeySpec(firstKeyInteger, secondKeyInteger));
// This always results in the public key OpenSSLRSAPublicKey{modulus=2b3b11f044.....58df890,publicExponent=10001}
Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWITHSHA1ANDMGF1PADDING");
String stringToEncode = "EncodeThisString";
byte[] bytesToEncode = stringToEncode.getBytes("UTF-8");
cipher.init(cipher.PUBLIC_KEY, publicKey);
byte[] encrypted = cipher.doFinal(plain);
The first challenge i'm struggling with is how to use the public key in iOS. Can I just dump the modulus into NSData and use it? Or must I store it in the keychain first? (I don't really need to use the keychain unless I must). Or is there a method similar to generatePublic() were I can recreate the public key using the 2 integers?
Then would I use SecKeyEncrypt to encrypt? Whenever I add this to my project I get Implicit declaration warnings even though I import the Security framework.
Thanks
EDIT -----
I think I have managed to get a Base64 encoded public key, which I believe is what is in a PEM certificate. Now, how to use it.
I am a total cryptography novice and was looking to have a simple (ha!) AESEncryption utility class that I could use for reading/writing files and string with AES keys. Something like:
String toEcnrypt = "This is a secret message!";
AESEcnryption aes = new AESEncryption(); // 256-bit by default
String encrypted = aes.encrypt(toEncrypt);
// Do some stuff
String shouldBeSameAsFirstString = aes.decrypt(encrypted);
The idea being that every time an AESEncryption is instantiated, a KeySpec is generated (and can be returned by the API for subsequent storage). Here's what I cooked up after examining the code of much, much brighter people than myself (so if you see your code here, thanks!):
public class AESEncryption {
private SecretKeySpec keySpec;
public AESEncryption()
{
super();
setKeySpec(AES256Encryption.generateAES256KeySpec());
}
// Uses 256-bit encryption by default.
public static SecretKeySpec generateAES256KeySpec()
{
// Stack variables
byte[] byteArray = new byte[16];
SecretKey oTmpKey = null;
KeyGenerator oKeyGen;
try
{
oKeyGen = KeyGenerator.getInstance("AES");
oKeyGen.init(256);
oTmpKey = oKeyGen.generateKey();
}
catch(Throwable oThrown)
{
throw new RuntimeException(oThrown);
}
byteArray = oTmpKey.getEncoded();
return new SecretKeySpec(byteArray, "AES");
}
public String encrypt(final String p_strPlaintext)
{
String strEncrypted = null;
try
{
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.ENCRYPT_MODE, keySpec);
strEncrypted = Base64.encodeBase64String(cipher
.doFinal(p_strPlaintext.getBytes()));
}
catch(Throwable oThrown)
{
System.out.println(oThrown.getMessage());
throw new RuntimeException(oThrown);
}
return strEncrypted;
}
}
For the Base64 En/Decoding I'm using Commons Codec - why? Because like I said I'm a crypto novice and that's the only thing I could find that seemed to get the job done!
When I use this code:
// This creates a unique key spec for this instance.
AESEncryption aes = new AESEncryption();
String toEncrypt = "blah";
// Throws a Throwable and prints the following to the console:
// "Illegal key size or default parameters"
String encrypted = aes.encrypt(toEncrypt);
I saw this question on SO where the asker had the same problem and I see that I may be missing the JCE. Knowing next to nothing about JCE, here's what I've collected:
The JCE is required for the AES algorithm to execute on the Java platform
The JCE downloads as a ZIP but really just contains two JARs
I put these 2 JARs (US_export_policy and local_policy) on my project's build path (Eclipse) and reran the code. Again the same problem. I know the linked article references installation instructions that recommended including these JARs in the JRE, but at runtime my app should only care about finding the JARs on the classpath - it shouldn't care about where it finds them on the classpath!
Is there anything I can do from inside Elcipse to make sure the JCE is available to my runtime classpath? Or am I way off base and have a bug in my code that is causing these errors?
You could simply use 128 bit AES keys. They are secure enough 99% of the time. Either that or use 256 bit keys and install the unlimited strength crypto files as indicated in the readme. If you could simply put them in the classpath everybody would simply copy the contents into their own libraries and skip the whole protection. They don't contain runnable code, just resources.
i'm pretty sure those jars are meaningless in the runtime classpath. they have to be installed in the jre installation dir.