I am making a solution for encryption of files via combination of RSA and AES. RSA is basically used for handshaking here to encrypt the symmetric key and decrypt at the receiver end via key pairs.
I have used Java Keystores for Private keys and Self-Signed certificates for Public Keys. Both are created as below:
KEY STORE
keytool -genkey -keyalg RSA -alias BANK -keystore receiverKeystore.jks -validity 360 -keysize 2048
CERTIFICATE EXPORT
keytool -export -alias BANK -keystore receiverKeystore.jks -rfc -file bankCert.cer
Similarly both are used in Java as below:
Private Key
private void setKeys() {
KeyStore senderKeyStore = null;
String keyStorePassword = "fms123";
try {
senderKeyStore = KeyStore.getInstance( "JKS" );
FileInputStream is = new FileInputStream( keystorePath );
senderKeyStore.load( is, keyStorePassword.toCharArray() );
senderPrivateKey = ( PrivateKey ) senderKeyStore.getKey( KEYSTORE_ALIAS, keyStorePassword.toCharArray() );
receiverPublicKey = loadReceiverPublicKeyFromCertificate();
}
catch ( Exception e ) {
System.out.println( " Exception in setting keys from key store = " + e );
}
}
PUBLIC KEY
private PublicKey loadReceiverPublicKeyFromCertificate () {
PublicKey publicKey = null;
try {
FileInputStream fin = new FileInputStream( certificateFilePath );
CertificateFactory factory = CertificateFactory.getInstance( "X.509" );
X509Certificate certificate = ( X509Certificate ) factory.generateCertificate( fin );
publicKey = certificate.getPublicKey();
}
catch ( Exception e ) {
System.out.println( " Exception in loading receiver certificate = " + e );
}
return publicKey;
}
I have now following Questions:
Is the the correct way do it?
If I use a trusted certificate here, how will the public key will be fetched from it? Will it be fetched in the same way? and for private keys? will I use the same approach or something from the certificate?
How can I distribute my certificate to the receiver? whats the secure communication shall for that?
I am new to encryption, will be very glad to get some advises.
Your handshaking is called key establishment. Handshaking is used for (online) transport modes, and I don't think you're developing that. I'd use a PKCS#12 key store instead, JKS is kinda deprecated. The whole idea of using a char[] for the password is that you can delete its contents directly after using it, so using a String as input is not a good idea. Strings are usually interned and always immutable in Java, so you cannot destroy them without ugly hacks involving native code.
Trusted certificates are no different from untrusted certificates. Yes, you can retrieve the public key from it. The private keys are commonly stored together with the certificate chain leading to the leaf user certificate that the private key belongs to. However, the private key is not part of the certificate, and therefore you cannot retrieve a private key from it. That's OK though, private keys should be established on the location where they are needed; there is no need to transport them other than for backup purposes.
How you can send the certificate to the receiver is up to you. The tricky part is to have the receiver trust the certificate. It's valid to use a pre-established key such as a trusted PGP key . If you haven't got such a key then you are in trouble though. What you can do is to use other methods to establish trust. Let the other party call you and then verify that the certificate fingerprint (basically just a hash over the certificate) is correct is probably one of the easiest ways. Bringing it over in person is of course also an option, if your users are not too far away.
You cannot create a secure channel out of thin air though. If you cannot trust who is on the other side, then establishing full trust is obviously impossible.
Related
I am looking to store sensitive passwords in a java keystore using java keytool's importpass. I am using Oracle java version 1.8.0_212, and cannot upgrade from java 8 at the moment.
I have created a PKCS12 keystore with the following command:
keytool -keystore test-keystore.p12 -genkey -storetype PKCS12 -alias test
I then imported a password into the keystore using:
keytool -importpass -storetype pkcs12 -alias protectedPass -keystore test-keystore.p12
This worked, but this uses the encryption algorithm "PBEWithMD5AndDES" by default, which isn't particularly secure. I am now trying to use "PBEWithHmacSHA256AndAES_128" from the Java Security Standard Algorithm Names doc but having issues getting this to work.
I've tried specifying the keyalg like this:
keytool -importpass -keyalg PBEWithHmacSHA256AndAES_128 -storetype pkcs12 -alias protectedPass -keystore test-keystore.p12 -v
and while this doesn't cause an error, it doesn't seem to actually affect the output. The secret is key still generated with PBEWithMD5AndDES:
D:\temp>keytool -importpass -keyalg PBEWithHmacSHA256AndAES_128 -storetype pkcs12 -alias protectedPass -keystore test-keystore.p12 -v
Enter keystore password:
Enter the password to be stored:
Re-enter password:
Generated PBEWithMD5AndDES secret key
[Storing test-keystore.p12]
I can see examples, such as in Java Keystores the Gory Details, of people using KeyStore.PasswordProtection to use algorithms like this, but I wanted to use the keytool if possible.
Am I missing something key here or trying to do something silly?
EDIT:
Tried to do something similar programmatically to figure out what I'm doing wrong and had no luck using PKCS12 keystores with this algorithm. However, jceks seems to work. I think this is to do with it using the SunJCE provider instead. Is there some parameter I am missing to get this algorithm working with pcks12? Or is there some other approach I could take?
I have provided some simple demo code below. If you swap "JCEKS" for "PKCS12" it will throw java.security.NoSuchAlgorithmException: unrecognized algorithm name: PBEWithHmacSHA256AndAES_128.
Demo Code:
static void encrypt() throws Exception {
KeyStore keyStore = KeyStore.getInstance("JCEKS");
keyStore.load(null, "changeit".toCharArray());
KeyStore.PasswordProtection keyStorePP = new KeyStore.PasswordProtection("changeit".toCharArray());
SecretKeyFactory pbeKeyFactory = SecretKeyFactory.getInstance("PBEWithHmacSHA256AndAES_128");
SecretKey pbeKey = pbeKeyFactory.generateSecret(new PBEKeySpec("testpassword".toCharArray()));
keyStore.setEntry(SECRET_KEY_ALIAS, new KeyStore.SecretKeyEntry(
pbeKey), keyStorePP);
FileOutputStream outputStream = new FileOutputStream(FILE_PATH);
keyStore.store(outputStream, "changeit".toCharArray());
}
static void decrypt() throws Exception {
KeyStore keyStore = KeyStore.getInstance("JCEKS");
FileInputStream fileInputStream = new FileInputStream(FILE_PATH);
keyStore.load(fileInputStream, "changeit".toCharArray());
KeyStore.PasswordProtection keyStorePP = new KeyStore.PasswordProtection("changeit".toCharArray());
KeyStore.SecretKeyEntry ske =
(KeyStore.SecretKeyEntry)keyStore.getEntry(SECRET_KEY_ALIAS, keyStorePP);
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBEWithHmacSHA256AndAES_128");
PBEKeySpec keySpec = (PBEKeySpec)factory.getKeySpec(
ske.getSecretKey(),
PBEKeySpec.class);
System.out.println(new String(keySpec.getPassword()));
}
You need to keep separate the algorithm a key is for and the way it is protected.
A Java keystore (depending on type, including JCEKS and PKCS12 but not JKS) can contain three kinds of entries. A SecretKeyEntry in general contains a secret key which is typically valid for a particular cipher algorithm, identified in the SecretKey object within the entry. But for password-based algorithms, the 'key' object is actually a generic 'PBEKey' which contains a password and AFAICT can be used for any PBE algorithm. It appears the 'algorithm' for such keys is actually stored as PBEwithMD5andDES, I suspect because that's the only scheme from original PKCS5 (i.e. PBES1) Java implements. However, my Oracle 8u212 keytool says more generically Generated PBE secret key in this case, not PBEwithMD5andDES as you show.
The stored secret key (and stored private key also) can be and normally is 'protected' in the keystore by being encrypted with a password-based encryption algorithm; this 'protection' algorithm is different from and in general not related to the algorithm(s) that the key or password will be used with after retrieval.
keytool -importpass -keyalg PBEWithHmacSHA256AndAES_128 attempts to specify the algorithm the key-really-password is nominally to be used with when retrieved, but not how it is protected, which for PKCS12 in most older versions including yours is actually PBE-SHA1-TripleDES (the same as for privatekeys) -- you can see this, with a little work, using OpenSSL. And in my 8u212 keytool specifying -keyalg fails for PKCS12 with an exception claiming it can't protect because there are no parameters for that algorithm (although I think the problem is serializing, not actually encrypting) much as you say your code does for PKCS12, but Java does have these parameters -- although in a different provider (SunJCE vs SunJSSE) which might be messing it up somehow; as you say JCEKS is in SunJCE.
Similarly your code generates a key to be used with that algorithm, but protected the default way. To change the protection you need to specify it in PasswordProtection, as stated in the page you link; see https://stackoverflow.com/a/47389384/ (by the same author) for an example.
Recent versions of Java -- 8u301 and 11.0.12 up -- have a new feature to configure the protection algorithms for PKCS12 with security properties in the java.security file which is in JRE/lib/security for 8 down or JRE/conf/security for 9 up. This applies to both code you write if not overridden as above, and to keytool (which can't override). However, those versions (except for 12 through 16, which are now EOL) also now default to PBEwithHmacSHA256andAES_256 which is already better than you asked for, so you probably don't need to change anything -- just use 8u301 up.
Note JCEKS uses a Sun-defined PBE-MD5-TripleDES similar to, but not, PBES1, while JKS doesn't support secretkey entries but for privatekey entries uses a deliberately weak (ITAR-friendly back in the 1990s) Sun-custom algorithm; these cannot be changed.
I am receiving the following String from a certificate stored in Azure Key Vault. I am using the Secret API in order to retrieve both the certificate and the private key related to this cert.
Initially the certificate was uploaded using a .pfx file to Azure Key vault. Now I need to create a Certificate and a PrivateKey to allow client authentication to a 3rd party system and I am using the given String retrieved from the API, however I am note sure how to get around that in Java.
I took some hints from this link in C# however I am pretty certain that this method doesn't work like that in Java. In particular an X509Certificate or a Certificate in general doesn't hold any information about the PrivateKey in Java, unlike C#, and I am not sure how to extract that information from given String in Java.
This works as expected to retrieve the certificate from the String retrieved from the API
String secret = azureSecret.getValue();
byte[] certkey = Base64.getDecoder().decode(secret);
ByteArrayInputStream inputStream = new ByteArrayInputStream(certkey);
CertificateFactory cf = CertificateFactory.getInstance("X.509");
Certificate cert = cf.generateCertificate(inputStream);
The azureSecret.getValue() format is like the following however I am not sure how to get PrivateKey out of the given String
MIIKvgIBaaZd6Euf3EYwYdHrIIKYzCC...
YES, Java X509Certificate and Certificate is only the certificate. Instead use KeyStore which can contain multiple entries each of which is either a 'trusted' certificate (for someone else), or a privatekey plus certificate plus other chain cert(s) (if applicable) for yourself, or (not relevant here) a 'secret' (symmetric) key. PKCS12 is supported as one type of KeyStore along with others not relevant here, so after the base64-decoding you already have do something like:
KeyStore ks = KeyStore.getInstance("PKCS12");
ks.load(inputstreamfromvaultvalue, password);
// then
PrivateKey pkey = (PrivateKey) ks.getKey(alias, password);
// and
Certificate cert = ks.getCertificate(alias); // if you only need the leaf cert
// or
Certificate[] chain = ks.getCertificateChain(alias); // usually
But if you want to do client authentication in TLS/SSL (including HTTPS), you give the JSSE KeyManager the whole keystore object not the individual pieces (privatekey and certificates). Similarly to verify the peer in TLS/SSL, you give TrustManager a keystore containing trusted certificates, usually root CAs and often defaulted to a built-in set of public root CAs.
Is it possible to change the certificate and certificate chain for existing entry (private key) in keystore. As I understand, firstly I need to delete existing entry and then store same private key with new certificate and chain. This solution is not an option since I don't know the password for that entry in keystore.
Solution so far:
Key generation and initial storage:
KeyPair kp = generateRSAKeyPair();
X509Certificate selfSignedCert = makeSelfSignedCert(kp);
ks.load(...);
ks.setKeyEntry("entry1", kp.getPrivate(), PASSWORD, new X509Certificate[]{selfSignedCert});
ks.store(...);
After CA sends certificate I want:
ks.load(...);
ks.setCertificateEntry("entry1", caSignedCert);
ks.store(...);
The main issue is that I don't have the right certificate at the time when the key is being stored to keystore. There is a delay between the generation of keys and when CA sends signed certificates.
Another option would be to not set certificate at all, and after CA signs CSR, add the final signed certificate to keystore. Something like:
KeyPair kp = generateRSAKeyPair();
ks.load(...);
ks.setKeyEntry("entry1", kp.getPrivate(), PASSWORD);
ks.store(...);
// after some time
ks.load(...);
ks.setCertificateEntry("entry1", caSignedCert);
ks.store(...);
But I couldn't find a way to store private key without an associated certificate in keystore. Storing private key elsewhere else is also not an option.
A potential solution is to store signed certificate from CA until a user provides the password to keystore entry and then deleting existing entry & creating new entry with proper certificates. This solution is not ideal and I would like to avoid going down this road.
Any suggestions are welcome.
The solution is to first store key to keystore with the self-signed certificate. At this time, this key cannot be used because CA did not sent the proper certificate for this key yet. If one would use key at this time for signing, the associated self-signed certificate would be used (which is not desirable).
After some time CA sends the proper certificate, which must be stored somewhere (database in my case).
On the first opportunity when the password for key entry is supplied, key is retrieved from keystore and immediately gets stored again with the same password but different alias and proper certificate from CA. Then it is safe to delete original entry with self-signed certificate.
I hope this helps someone. Any better solutions are welcome.
I am working on a distributed application with a number of uniquely identified slave processes that will communicate with a master application via SSL enabled sockets. The application is written in java.
I need some help understanding SSLSockets, or rather, the certificates they use.
What i am looking for is someone who can tell me if i have understood the basic workings of certificate chains correctly, but i wouldn't say no to a code sample either.
I would like a setup where the server itself has a CA signed certificate, and every slave will get their own certificate created by the master application.
CA->Main server cert->Master SSL cert
CA->Main server cert->Slave SSL cert 1
CA->Main server cert->Slave SSL cert 2
CA->Main server cert->Slave SSL cert 3
First question: Is this kind of certificate chain the correct way to tackle the problem?
I am thinking this is the simplest way of achieving the master and slaves all have a unique identity without having to CA sign every certificate.
Second question:
How do i programatically go about creating an SSL certificate in java? I am trying to create the last certificate in the chain here, assuming i already have the "Main server cert" for now.
I have gotten so far as generating a key for the certificate (Where type is RSA):
public KeyPair generateKeypair(String type, int bytes)
throws NoSuchAlgorithmException{
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(type);
keyPairGenerator.initialize(bytes);
return keyPairGenerator.generateKeyPair();
}
X509Principal issuer = PrincipalUtil.getSubjectX509Principal(serverCert);
SubjectPublicKeyInfo key
= SubjectPublicKeyInfo.getInstance(kpair.getPublic().getEncoded());
X509v3CertificateBuilder certGen
= new X509v3CertificateBuilder(
issuer,
BigInteger.valueOf(new SecureRandom().nextInt()),
before,
after,
subject,
key
);
AlgorithmIdentifier sigAlgId
= new DefaultSignatureAlgorithmIdentifierFinder().find("SHA1withRSA");
AlgorithmIdentifier digAlgId
= new DefaultDigestAlgorithmIdentifierFinder().find(sigAlgId);
I don't assume that setting the serverCert as the issuer is enough to sign the certificate? As far as i have understood i need to sign the new certificate with the next certificate in the chain somehow, but how do i do that? Do i sign the certificate with the serverCert's private key like:
AsymmetricKeyParameter akp
= PrivateKeyFactory.createKey(serverPrivateKey.getEncoded());
AlgorithmIdentifier sigAlgId
= new DefaultSignatureAlgorithmIdentifierFinder().find("SHA1withRSA");
AlgorithmIdentifier digAlgId
= new DefaultDigestAlgorithmIdentifierFinder().find(sigAlgId);
ContentSigner sigGen
= new BcRSAContentSignerBuilder(sigAlgId, digAlgId).build(akp);
Are there any other steps i missed?
From a technical point of view your solution is correct. However do not forget the security considerations: who can request a certificate, how the authentication is performed, how the certificates/private keys are distributed to the servers...
These elements are mandatory for a certificate generation:
Subject Name
Issuer name
certificate serial number
subject public key
validity dates (not before, not after)
It is also a good practice to add some extensions:
Subject Key Identifier
Authority Key Indentifier
Basic Constraints
Key Usage
Extended Key Usages
This code snippet outlines the certificate generation:
ContentSigner getCertSigner(PrivateKey issuerKey) {
AsymmetricKeyParameter akp = PrivateKeyFactory.createKey(issuerKey.getEncoded());
AlgorithmIdentifier sigAlgId = new DefaultSignatureAlgorithmIdentifierFinder().find("SHA1withRSA");
AlgorithmIdentifier digAlgId = new DefaultDigestAlgorithmIdentifierFinder().find(sigAlgId);
return new BcRSAContentSignerBuilder(sigAlgId, digAlgId).build(akp);
}
X509CertificateHolder generateCertificate(X509Certificate issuerCert, PrivateKey issuerKey, X500Name subject, PublicKey subjectKey, Date notBefore, Date notAfter) {
X509Principal issuerDN = PrincipalUtil.getSubjectX509Principal(issuerCert);
SubjectPublicKeyInfo key = SubjectPublicKeyInfo.getInstance(subjectKey.getEncoded());
X509v3CertificateBuilder builder = new X509v3CertificateBuilder(issuerDN, BigInteger.valueOf(new SecureRandom().nextInt()), before, after, subject, key);
// Add authority key identifier
builder.addExtension(X509Extension.authorityKeyIdentifier, false, JcaX509ExtensionUtils.createAuthorityKeyIdentifier(issuerCert));
// Add subject key identifier
builder.addExtension(X509Extension.subjectKeyIdentifier, false, JcaX509ExtensionUtils.createSubjectKeyIdentifier(subjectKey));
// Add basic constraints
builder.addExtension(X509Extension.basicConstraints, true, new BasicConstraints(false));
// Add key usage
KeyUsage keyUsage = new KeyUsage(KeyUsage.keyEncipherment|KeyUsage.digitalSignature);
builder.addExtension(X509Extension.keyUsage, true, keyUsage);
// Add extended key usage
ExtendedKeyUsage extKeyUsage = new ExtendedKeyUsage(KeyPurposeId.id_kp_serverAuth);
builder.addExtension(X509Extension.extendedKeyUsage, false, extKeyUsage);
return builder.build(getCertSigner(issuerKey));
}
UPDATE: fixed the code according to Martin Nielsen's comment.
I have PEM format file, How can verify the signature in Java, as I followed http://download.oracle.com/javase/tutorial/security/apisign/versig.html but found that Java doesnt support PEM
You can read a certificate in a PEM file using BouncyCastle's PEMReader. If the content is an X.509 certificate, you should get an instance of X509Certificate and verify it as you want from there.
EDIT: Here is what the code should look like (not tried):
// The key with which you want to verify the cert.
// This is probably a CA certificate's public key.
PublicKey publicKey = ...;
PEMReader reader = new PEMReader(new FileReader("/path/to/file.pem"));
Object pemObject = reader.readObject();
if (pemObject instanceof X509Certificate) {
X509Certificate cert = (X509Certificate)pemObject;
cert.checkValidity(); // to check it's valid in time
cert.verify(publicKey); // verify the sig. using the issuer's public key
}
(Of course, as with any I/O operations, you'll need to close the reader perhaps with try/finally.)
Note that checkValidity and verify don't return anything: instead, they throw exceptions if when they fail.