Create CipherOutputStream with PGP Bouncy Castle dependency - java

I want to create a OutputStream from another OutputStream in which the new OutputStream will automatically encrypt the content I write to that OutputStream. I want to use Bouncy Castle since I am already using that dependency for other functionality.
I see various questions over the internet how to encrypt data with Bouncy Castle, but the answers either encrypt a given File (I don't use files, I use OutputStreams) or have a huge amount of code I need to copy paste. I can not believe it must be that difficult.
This is my setup:
I am using this Bouncy Castle dependency (V1.68)
I am using Java 8
I have a public and private key generated by https://pgpkeygen.com/. The algorithm is RSA and the keysize is 1024.
I saved the public key and private key as a file on my machine
I want to make sure the test below passes
I have some code commented out, the init function on Cipher (the code compiles, but the test fails). I don't know what I should put in as second argument in the init function.
The read functions are from: https://github.com/jordanbaucke/PGP-Sign-and-Encrypt/blob/472d8932df303d6861ec494a3e942ea268eaf25f/src/SignAndEncrypt.java#L272. Only the testEncryptDecryptWithoutSigning is writting by me.
Code:
#Test
void testEncryptDecryptWithoutSigning() throws Exception {
// The data will be written to this property
ByteArrayOutputStream baos = new ByteArrayOutputStream();
Security.addProvider(new BouncyCastleProvider());
PGPSecretKey privateKey = readSecretKey(pathToFile("privatekey0"));
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
//cipher.init(Cipher.ENCRYPT_MODE, privateKey);
CipherOutputStream os = new CipherOutputStream(baos, cipher);
// I also need to use a PrintWriter
PrintWriter printWriter =
new PrintWriter(new BufferedWriter(new OutputStreamWriter(
os,
StandardCharsets.UTF_8.name())));
// This is an example of super secret data to write
String data = "Some very sensitive data";
printWriter.print(data);
printWriter.close();
// At this point, the data is 'inside' the byte array property
// Assert the text is encrypted
if (baos.toString(StandardCharsets.UTF_8.name()).equals(data)) {
throw new RuntimeException("baos not encrypted");
}
PGPSecretKey publicKey = readSecretKey(pathToFile("publickey0"));
//cipher.init(Cipher.DECRYPT_MODE, publicKey);
ByteArrayInputStream inputStream = new ByteArrayInputStream(baos.toByteArray());
ByteArrayOutputStream decrypted = new ByteArrayOutputStream();
// Decrypt the stream, but how?
if (!decrypted.toString(StandardCharsets.UTF_8.name()).equals(data)) {
throw new RuntimeException("Not successfully decrypted");
}
}
static PGPSecretKey readSecretKey(InputStream input) throws IOException, PGPException
{
PGPSecretKeyRingCollection pgpSec = new PGPSecretKeyRingCollection(
PGPUtil.getDecoderStream(input), new JcaKeyFingerprintCalculator());
//
// we just loop through the collection till we find a key suitable for encryption, in the real
// world you would probably want to be a bit smarter about this.
//
Iterator keyRingIter = pgpSec.getKeyRings();
while (keyRingIter.hasNext())
{
PGPSecretKeyRing keyRing = (PGPSecretKeyRing)keyRingIter.next();
Iterator keyIter = keyRing.getSecretKeys();
while (keyIter.hasNext())
{
PGPSecretKey key = (PGPSecretKey)keyIter.next();
if (key.isSigningKey())
{
return key;
}
}
}
throw new IllegalArgumentException("Can't find signing key in key ring.");
}
static PGPSecretKey readSecretKey(String fileName) throws IOException, PGPException
{
InputStream keyIn = new BufferedInputStream(new FileInputStream(fileName));
PGPSecretKey secKey = readSecretKey(keyIn);
keyIn.close();
return secKey;
}
static PGPPublicKey readPublicKey(String fileName) throws IOException, PGPException
{
InputStream keyIn = new BufferedInputStream(new FileInputStream(fileName));
PGPPublicKey pubKey = readPublicKey(keyIn);
keyIn.close();
return pubKey;
}
/**
* A simple routine that opens a key ring file and loads the first available key
* suitable for encryption.
*
* #param input data stream containing the public key data
* #return the first public key found.
* #throws IOException
* #throws PGPException
*/
static PGPPublicKey readPublicKey(InputStream input) throws IOException, PGPException
{
PGPPublicKeyRingCollection pgpPub = new PGPPublicKeyRingCollection(
PGPUtil.getDecoderStream(input), new JcaKeyFingerprintCalculator());
//
// we just loop through the collection till we find a key suitable for encryption, in the real
// world you would probably want to be a bit smarter about this.
//
Iterator keyRingIter = pgpPub.getKeyRings();
while (keyRingIter.hasNext())
{
PGPPublicKeyRing keyRing = (PGPPublicKeyRing)keyRingIter.next();
Iterator keyIter = keyRing.getPublicKeys();
while (keyIter.hasNext())
{
PGPPublicKey key = (PGPPublicKey)keyIter.next();
if (key.isEncryptionKey())
{
return key;
}
}
}
throw new IllegalArgumentException("Can't find encryption key in key ring.");
}

As a preliminary, that website doesn't generate a keypair, but three. Historically in PGP there has long been some ambiguity between actual cryptographic keys and keypairs, and what PGP users call keys, because it is common for a given user (or entity or role etc) to have one 'master' or 'primary' key and one or more subkey(s) tied to that masterkey. For DSA+ElG keys it was technically necessary to use a subkey (and not the masterkey) for encryption; for RSA it is considered good practice to do so because it is often better to manage (e.g. potentially revoke) these keys separately. Some people also consider it good practice to use a subkey rather than the masterkey for signing data, and use the masterkey only for signing keys (which PGP calls certifying - C), but some don't. When PGP users and documents talk about a 'key' they often mean the group of a masterkey and (all) its subkey(s), and they say masterkey or subkey (or encryption subkey or signing subkey) to mean a specific actual key.
When you choose RSA that website generates a masterkey (keypair) with usage SCEA -- i.e. all purposes -- AND TWO subkeys each with usage SEA -- all purposes valid for a subkey. This is nonsensical; if the masterkey supports Signing and Encryption most PGP programs will never use any subkey(s), and even if it didn't or you override it, there is no meaningful distinction between the subkeys and no logical way to choose which to use.
And BouncyCastle exacerbates this by changing the terminology: most PGP programs use key for either an actual key or a group of masterkey plus subkeys as above, and 'public' and 'secret' key to refer to the halves of each key or group, and 'keyring' to refer to all the key group(s) you have stored, typically in a file, which might be for many different people or entities. Bouncy however calls the group of a masterkey with its subkeys (in either public or secret form) a KeyRing, and the file containing possibly multiple groups a KeyRingCollection, both of them in Public and Secret variants. Anyway ...
Your first problem is you have it backwards. In public key cryptography we encrypt with the public key (half) and decrypt with the private key (half) which PGP (and thus BCPG) calls secret. Further, because private/secret keys in PGP are password-encrypted, to use it we must first decrypt it. (The same is true in 'normal' JCA keystores like JKS and PKCS12, but not necessarily in others.)
Your second problem is the types. Although a (specific) PGP key for a given asymmetric algorithm is semantically just a key for that algorithm, plus some metadata (identity, preference, and trust/signature information), the Java objects (classes) in BCPG for PGP keys are not in the type hierarchy of the objects used for keys in Java Crypto Architecture (JCA). In simpler words, org.bouncycastle.openpgp.PGPPublicKey is not a subclass of java.security.PublicKey. So these key objects must be converted to JCA-compatible objects to be used with JCA.
With those changes and some additions, the following code works (FSVO work):
static void SO66155608BCPGPRawStream (String[] args) throws Exception {
byte[] plain = "testdata".getBytes(StandardCharsets.UTF_8);
PGPPublicKey p1 = null;
FileInputStream is = new FileInputStream (args[0]);
Iterator<PGPPublicKeyRing> i1 = new JcaPGPPublicKeyRingCollection (PGPUtil.getDecoderStream(is)).getKeyRings();
for( Iterator<PGPPublicKey> j1 = i1.next().getPublicKeys(); j1.hasNext(); ){
PGPPublicKey t1 = j1.next();
if( t1.isEncryptionKey() ){ p1 = t1; break; }
}
is.close();
if( p1 == null ) throw new Exception ("no encryption key");
PublicKey k1 = new JcaPGPKeyConverter().getPublicKey(p1);
Cipher c1 = Cipher.getInstance("RSA/ECB/PKCS1Padding");
c1.init(Cipher.ENCRYPT_MODE, k1);
ByteArrayOutputStream b1 = new ByteArrayOutputStream();
CipherOutputStream s1 = new CipherOutputStream(b1,c1);
s1.write(plain);
s1.close();
byte[] cipher = b1.toByteArray();
long id = p1.getKeyID();
System.out.println("keyid="+Long.toString(id,16)+" "+Arrays.toString(cipher));
if( Arrays.equals(cipher,plain) ) throw new Exception ("didn't encrypt!");
PGPSecretKey p2 = null;
is = new FileInputStream (args[1]);
Iterator<PGPSecretKeyRing> i2 = new JcaPGPSecretKeyRingCollection (PGPUtil.getDecoderStream(is)).getKeyRings();
for( Iterator<PGPSecretKey> j2 = i2.next().getSecretKeys(); j2.hasNext(); ){
PGPSecretKey t2 = j2.next();
if( t2.getKeyID() == id ){ p2 = t2; break; }
}
is.close();
if( p2 == null ) throw new Exception ("no decryption key");
PGPPrivateKey p3 = p2.extractPrivateKey(new JcePBESecretKeyDecryptorBuilder().build(args[2].toCharArray()));
PrivateKey k2 = new JcaPGPKeyConverter().getPrivateKey(p3);
Cipher c2 = Cipher.getInstance("RSA/ECB/PKCS1Padding");
c2.init(Cipher.DECRYPT_MODE, k2);
ByteArrayInputStream b2 = new ByteArrayInputStream(cipher);
CipherInputStream s2 = new CipherInputStream(b2,c2);
byte[] back = new byte[cipher.length]; // definitely more than needed
int actual = s2.read(back);
s2.close();
System.out.println ("Result->" + new String(back,0,actual,StandardCharsets.UTF_8));
}
(I find it clearer to have the code in one place in execution sequence, but you can break it out into pieces as you had it with no substantive change.)
I kept your logic (from Bouncy examples) of choosing the first encryption-capable public key either master or sub from the first group having one which per above Bouncy miscalls a KeyRing; since per above the website you used gives the masterkey SCEA this is always the masterkey. It isn't possible to similarly select a secret/private key depending on whether it allows encryption, and in any case there is no guarantee that the public key file will always be in the same order, so the correct way to choose the decryption key is to match the keyid from the key that was used for encryption.
Also, modern encryption algorithms (both asymmetric like RSA and symmetric like AES or '3DES') produce data that is arbitrary bit patterns, and in particular mostly NOT valid UTF-8, so 'decoding' those bytes as UTF-8 to compare to the plaintext is generally going to corrupt your data; if you want this (unnecessary) check you should instead compare the byte arrays as I show.
Finally, in case you don't know, asymmetric algorithms are not normally used to encrypt data of large or variable size, which is what you would normally use Java streams for; this is also explained in the wikipedia article. This approach, using RSA PKCS1-v1_5 directly, with a 1024-bit key as you have, can only handle 117 bytes of data (which may be fewer than 117 characters, depending).
And if you expect the result to be compatible or interoperable with any real PGP implementation, it definitely isn't -- which means the effort of converting from PGP key format is wasted, because you could have simply generated JCA-form keys directly in the first place, following the basic tutorials on the Oracle website or hundreds of examples here on Stack. If you want to interoperate with GPG or similar, you need to use the BCPG classes for PGP-format encryption and decryption, which can layer on plain byte streams, but are completely different from and incompatible with JCA's Cipher{Input,Output}Stream.

Related

Is it possible to generate a 64-byte (256-bit) key and store/retrieve it with AndroidKeyStore?

In my Android app, I need a way to encrypt the data I store in a local DB.
I chose Realm DB because the offer a seamless integration with encryption. I just need to pass a key when initializing the Realm instance. This key must be of 64 byte size.
For security reason, I found out that the best way to store this key is in AndroidKeyStore. I'm struggling to find a way to generate a key (using any algorithm) with that size, and getting it into a 64-byte array. I'm trying to keep a minSdk of API 19, but I believe I can bump it up to 23 if needed (many changes to AndroidKeyStore between these two versions).
Does anyone have an idea? Here is my code:
Class Encryption.java
private static KeyStore ks = null;
private static String ALIAS = "com.oi.pap";
public static byte[] loadkey(Context context) {
byte[] content = new byte[64];
try {
if (ks == null) {
createNewKeys(context);
}
ks = KeyStore.getInstance("AndroidKeyStore");
ks.load(null);
content= ks.getCertificate(ALIAS).getEncoded(); //<----- HERE, I GET SIZE GREATER THAN 64
Log.e(TAG, "original key :" + Arrays.toString(content));
} catch (KeyStoreException | CertificateException | IOException | NoSuchAlgorithmException e) {
e.printStackTrace();
}
content = Arrays.copyOfRange(content, 0, 64); //<---- I would like to remove this part.
return content;
}
private static void createNewKeys(Context context) throws KeyStoreException, CertificateException, NoSuchAlgorithmException, IOException {
ks = KeyStore.getInstance("AndroidKeyStore");
ks.load(null);
try {
// Create new key if needed
if (!ks.containsAlias(ALIAS)) {
Calendar start = Calendar.getInstance();
Calendar end = Calendar.getInstance();
end.add(Calendar.YEAR, 1);
KeyPairGeneratorSpec spec = new KeyPairGeneratorSpec.Builder(context)
.setAlias(ALIAS)
.setSubject(new X500Principal("CN=PapRealmKey, O=oipap"))
.setSerialNumber(BigInteger.ONE)
.setStartDate(start.getTime())
.setEndDate(end.getTime())
.setKeySize(256)
.setKeyType(KeyProperties.KEY_ALGORITHM_EC)
.build();
KeyPairGenerator generator = KeyPairGenerator
.getInstance(KeyProperties.KEY_ALGORITHM_RSA, "AndroidKeyStore");
generator.initialize(spec);
KeyPair keyPair = generator.generateKeyPair();
Log.e(TAG, "generated key :" + Arrays.toString(keyPair.getPrivate().getEncoded()));
}
} catch (Exception e) {
Log.e(TAG, Log.getStackTraceString(e));
}
}
The point of AndroidKeyStore is to move sensitive key material out of your app, out of the operating system and into secure hardware where it can never leak or be compromised. So, by design, if you create a key in AndroidKeyStore, you can never get the key material out.
In this case, Realm DB wants the secret key material, so you can't give it an AndroidKeyStore key. Also, what Realm wants is two AES keys, not an EC key, as you were trying to generate.
The right way to generate the key material you need is:
byte[] dbKey = new byte[64];
Random random = new SecureRandom();
random.nextBytes(dbKey);
// Pass dbKey to Realm DB...
Arrays.fill(dbKey, 0); // Wipe key after use.
Just 64 random bytes. However, you're going to need to store those bytes somewhere. You could create an AES key with AndroidKeyStore and use it to encrypt dbKey. Something like:
KeyGenerator keyGenerator = KeyGenerator.getInstance(
KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore");
keyGenerator.init(
new KeyGenParameterSpec.Builder("dbKeyWrappingKey",
KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
.setBlockModes(KeyProperties.BLOCK_MODE_GCM)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
.build());
SecretKey key = keyGenerator.generateKey();
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
cipher.init(Cipher.ENCRYPT_MODE, key);
byte[] iv = cipher.getIV();
byte[] encryptedDbKey = cipher.doFinal(dbKey);
You'll need to save both iv and encryptedDbKey somewhere (not in the database!) so that you can recover dbKey. Then you can decrypt it with:
KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
keyStore.load(null);
key = (SecretKey) keyStore.getKey("dbKeyWrappingKey", null);
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
cipher.init(Cipher.DECRYPT_MODE, key, new GCMParameterSpec(128, iv));
byte[] dbKey = cipher.doFinal(encryptedDbKey);
// Pass dbKey to Realm DB and then wipe it.
However, with all of that said... I don't think you should do any of it. I don't think this actually gives you any security that Android doesn't give you by default anyway. If an attacker tries to dump the device storage, which contains your database, he'll get nothing because Android encrypts all of the storage anyway. If an attacker can root the device, he can run code as your app and use it to decrypt dbKey the same way your app does.
Where AndroidKeyStore may really add value is if you add some additional protections on dbKeyWrappingKey. For example, if you set it to require user authentication within, say five minutes, it will only be possible to use dbWrappingKey to decrypt dbKey when the user is around to enter their PIN/pattern/password or touch the fingerprint scanner. Note that this only works if the user has a PIN/pattern/password, but if they don't, well, your database is wide open to anyone who picks up the phone anyway.
See KeyGenParameterSpec for all of the things you can do to restrict the ways dbKeyWrappingKey can be used.
As far as I know, the usual way to solve this problem is, that you generate your own random key of the size you need (master-key), and this master-key can be encrypted with the help of the key store.
Generate your own random master-key of the size you need.
Encrypt data with this master-key (e.g. symmetric encryption).
Encrypt the master-key with the help of the key-store.
Store the encrypted master-key somewhere.
To decrypt your data:
Read the encrypted master-key.
Decrypt the master-key with the help of the key-store.
Decrypt data with the master-key.
In other words, it is not the master-key which is stored inside the key-store, but the key-store can be used to protect/encrypt your master-key.

How to encrypt RSA private key with PBE in PKCS#5 format in Java with IAIK JCE?

I've created an RSA Key Pair. Now, I'm trying to encrypt the private key with a DES algorithm, format it to PKCS#5 and print it on the console. Unfortunately, the generated private key does not work. When I try to use it, after entering the right passphrase, the ssh client returns the passphrase is not valid:
Load key "test.key": incorrect passphrase supplied to decrypt private key
Could please someone tells me where I'm wrong?
This is the code:
private byte[] iv;
public void generate() throws Exception {
RSAKeyPairGenerator generator = new RSAKeyPairGenerator();
generator.initialize(2048);
KeyPair keyPair = generator.generateKeyPair();
String passphrase = "passphrase";
byte[] encryptedData = encrypt(keyPair.getPrivate().getEncoded(), passphrase);
System.out.println(getPrivateKeyPem(Base64.encodeBase64String(encryptedData)));
}
private byte[] encrypt(byte[] data, String passphrase) throws Exception {
String algorithm = "PBEWithMD5AndDES";
salt = new byte[8];
int iterations = 1024;
// Create a key from the supplied passphrase.
KeySpec ks = new PBEKeySpec(passphrase.toCharArray());
SecretKeyFactory skf = SecretKeyFactory.getInstance(algorithm);
SecretKey key = skf.generateSecret(ks);
// Create the salt from eight bytes of the digest of P || M.
MessageDigest md = MessageDigest.getInstance("MD5");
md.update(passphrase.getBytes());
md.update(data);
byte[] digest = md.digest();
System.arraycopy(digest, 0, salt, 0, 8);
AlgorithmParameterSpec aps = new PBEParameterSpec(salt, iterations);
Cipher cipher = Cipher.getInstance(AlgorithmID.pbeWithSHAAnd3_KeyTripleDES_CBC.getJcaStandardName());
cipher.init(Cipher.ENCRYPT_MODE, key, aps);
iv = cipher.getIV();
byte[] output = cipher.doFinal(data);
ByteArrayOutputStream out = new ByteArrayOutputStream();
out.write(salt);
out.write(output);
out.close();
return out.toByteArray();
}
private String getPrivateKeyPem(String privateKey) throws Exception {
StringBuffer formatted = new StringBuffer();
formatted.append("-----BEGIN RSA PRIVATE KEY----- " + LINE_SEPARATOR);
formatted.append("Proc-Type: 4,ENCRYPTED" + LINE_SEPARATOR);
formatted.append("DEK-Info: DES-EDE3-CBC,");
formatted.append(bytesToHex(iv));
formatted.append(LINE_SEPARATOR);
formatted.append(LINE_SEPARATOR);
Arrays.stream(privateKey.split("(?<=\\G.{64})")).forEach(line -> formatted.append(line + LINE_SEPARATOR));
formatted.append("-----END RSA PRIVATE KEY-----");
return formatted.toString();
}
private String bytesToHex(byte[] bytes) {
char[] hexArray = "0123456789ABCDEF".toCharArray();
char[] hexChars = new char[bytes.length * 2];
for (int j = 0; j < bytes.length; j++) {
int v = bytes[j] & 0xFF;
hexChars[j * 2] = hexArray[v >>> 4];
hexChars[j * 2 + 1] = hexArray[v & 0x0F];
}
return new String(hexChars);
}
And this is the generated private key in PKCS#5 PEM format:
-----BEGIN RSA PRIVATE KEY-----
Proc-Type: 4,ENCRYPTED
DEK-Info: DES-EDE3-CBC,CA138D5D3C048EBD
+aZNZJKLvNtlmnkg+rFK6NFm45pQJNnJB9ddQ3Rc5Ak0C/Igm9EqHoOS+iy+PPjx
pEKbhc4Qe3U0GOT9L5oN7iaWL82gUznRLRyUXtOrGcpE7TyrE+rydD9BsslJPCe+
y7a9LnSNZuJpJPnJCeKwzy5FGVv2KmDzGTcs9IqCMKgV69qf83pOJU6Dk+bvh9YP
3I05FHeaQYQk8c3t3onfljVIaYOfbNYFLZgNgGtPzFD4OpuDypei/61i3DeXyFUA
SNSY5fPwp6iSeSKtwduSEJMX31TKSpqWeZmEmMNcnh8oZz2E0jRWkbkaFuZfNtqt
aVpLN49oRpbsij+i1+udyuIXdBGRYt9iDZKnw+LDjC3X9R2ceq4AOdfsmEVYbO1i
YNms9eXSkANuchiI2YqkKsCwqI5S8S/2Xj76zf+pCDhCTYGV3RygkN6imX/Qg2eF
LOricZZTF/YPcKnggqNrZy4KSUzAgZ9NhzWCWOCiGFcQLYIo+qDoJ8t4FwxQYhx9
7ckzXML0n0q5ba5pGekLbBUJ9/TdtnqfqmYrHX+4OlrR7XAu478v2QH6/QtNKdZf
VRTqmKKH0n8JL9AgaXWipQstW5ERNZJ9YPBASQzewVNLv4gRZRTw8bYcU/hiPbWp
eqULYYI9324RzY3UTsz3N9X+zQsT02zNdxud7XmmoHL493yyvqT9ERmF4uckGYei
HZ16KFeKQXE9z+x0WNFAKX3nbttVlN5O7TAmUolFTwu11UDsJEjrYMZRwjheAZyD
UnV1LwhFT+QA0r68Mto3poxpAawCJqPP50V4jbhsOb0J7sxT8fo2mBVSxTdb9+t1
lG++x/gHcK51ApK1tF1FhRRKdtOzSib376Kmt23q0jVDNVyy09ys+8LRElOAY1Es
LIuMMM3F7l+F4+knKh3/IkPZwRIz3f9fpsVYIePPS1bUdagzNoMqUkTwzmq6vmUP
C5QvN6Z5ukVCObK+T8C4rya8KQ/2kwoSCRDIX6Mzpnqx6SoO4mvtBHvPcICGdOD6
aX/SbLd9J2lenTxnaAvxWW0jkF6q9x9AAIDdXTd9B5LnOG0Nq+zI+6THL+YpBCB9
6oMO4YChFNoEx0HZVdOc8E7xvXU2NqinmRnyh7hCR5KNfzsNdxg1d8ly67gdZQ1Q
bk1HPKvr6T568Ztapz1J/O6YWRIHdrGyA6liOKdArhhSI9xdk3H3JFNiuH+qkSCB
0mBYdS0BVRVdKbKcrk4WRHZxHsDsQn1/bPxok4dCG/dGO/gT0QlxV+hOV8h/4dJO
mcUvzdW4I8XKrX5KlTGNusVRiFX3Cy8FFZQtSxdWzr6XR6u0bUKS+KjDl1KoFxPH
GwYSTkJVE+fbjsSisQwXjWnwGGkNDuQ1IIMJOAHMK4Mly1jMdFF938WNY7NS4bIb
IXXkRdwxhdkRDiENSMXY8YeCNBJMjqdXZtR4cwGEXO+G+fpT5+ZrfPbQYO+0E0r4
wGPKlrpeeR74ALiaUemUYVIdw0ezlGvdhul2KZx4L82NpI6/JQ7shq9/BEW2dWhN
aDuWri2obsNL3kk2VBWPNiE6Rn/HtjwKn7ioWZ3IIgOgyavcITPBe0FAjxmfRs5w
VWLFBXqcyV9cu1xS4GoCNLk0MrVziUCwHmwkLIzQZos=
-----END RSA PRIVATE KEY-----
Thanks in advance.
There is no such thing as PKCS#5 format. PKCS#5 primarily defines two password-based key derivation functions and password-based encryption schemes using them, plus a password-based MAC scheme, but does not define any format for the data. (It does define ASN.1 OIDs for these operations, and ASN.1 structures for their parameters -- primarily PBKDF2 and PBES2, because the only parameter for PBKDF1 and PBES1 is the salt.) PKCS#5 also defines a padding scheme for the CBC mode data encryption; this padding was slightly enhanced by PKCS#7 and used by many other applications which usually call it PKCS5 padding or PKCS7 padding. None of these are data formats, and none of them involves RSA (or other) private keys as such.
The file format you apparently want is the one used by OpenSSH (for a long time always, then for the last few years as the default, until OpenSSH 7.8 just a month ago made it optional) and as a result also used by other software that wants to be compatible or even interchangeable with OpenSSH. This format is actually defined by OpenSSL, which OpenSSH has long used for most of its cryptography. (Following Heartbleed, OpenSSH created a fork of OpenSSL called LibreSSL, which tries to be more robust and secure internally but intentionally maintains the same external interfaces and formats, and in any case hasn't been widely adopted.)
It is one of several 'PEM' formats defined by OpenSSL, and is mostly described on the man page for a number of 'PEM' routines including PEM_write[_bio]_RSAPrivateKey -- on your system if you have OpenSSL and it's not Windows, or on the web with the encryption part near the end in the section 'PEM ENCRYPTION FORMAT', and the EVP_BytesToKey routine it references similarly on its own man page. In short:
it does not use the pbeSHAwith3_keyTripleDES-CBC (meaning SHA1) scheme defined by PKCS#12/rfc7292 or the pbeMD5withDES-CBC scheme defined by PKCS#5/rfc2898 in PBES1. Instead it uses EVP_BytesToKey (which is partly based on PBKDF1) with md5 and 1 iteration, and salt equal to the IV, to derive the key, and then encrypts/decrypts with any supported symmetric cipher mode that uses an IV (thus not stream or ECB) but usually defaulting to DES-EDE3 (aka 3key-TripleDES) CBC as you ask for. Yes, EVP_BytesToKey with niter=1 is a poor PBKDF and makes these files insecure unless you use a very strong password; there are numerous Qs about that already.
And finally the plaintext of this file format is not the PKCS#8 (generic) encoding returned by [RSA]PrivateKey.getEncoded() but rather the RSA-only format defined by PKCS#1/rfc8017 et pred. And the empty line between the Proc-type and DEK-info headers and the base64 is required, and the line terminator on the dashes-END line may be needed depending on what software does the reading.
The easiest way to do this is to use software already compatible with OpenSSL private-key PEM format(s), including OpenSSL itself. Java can run an external program: OpenSSH's ssh-keygen if you have it, or openssl genrsa if you have that. The BouncyCastle bcpkix library supports this and other OpenSSL PEM formats. If 'ssh client' is jsch, that normally reads keyfiles in several formats including this one, but com.jcraft.jsch.KeyPairRSA actually supports generating a key and writing it in this PEM format as well. Puttygen also supports this format, but the other formats it can convert from and to aren't Java-friendly. I'm sure there are more.
But if you need to do it in your own code, here's how:
// given [RSA]PrivateKey privkey, get the PKCS1 part from the PKCS8 encoding
byte[] pk8 = privkey.getEncoded();
// this is wrong for RSA<=512 but those are totally insecure anyway
if( pk8[0]!=0x30 || pk8[1]!=(byte)0x82 ) throw new Exception();
if( 4 + (pk8[2]<<8 | (pk8[3]&0xFF)) != pk8.length ) throw new Exception();
if( pk8[4]!=2 || pk8[5]!=1 || pk8[6]!= 0 ) throw new Exception();
if( pk8[7] != 0x30 || pk8[8]==0 || pk8[8]>127 ) throw new Exception();
// could also check contents of the AlgId but that's more work
int i = 4 + 3 + 2 + pk8[8];
if( i + 4 > pk8.length || pk8[i]!=4 || pk8[i+1]!=(byte)0x82 ) throw new Exception();
byte[] old = Arrays.copyOfRange (pk8, i+4, pk8.length);
// OpenSSL-Legacy PEM encryption = 3keytdes-cbc using random iv
// key from EVP_BytesToKey(3keytdes.keylen=24,hash=md5,salt=iv,,iter=1,outkey,notiv)
byte[] passphrase = "passphrase".getBytes(); // charset doesn't matter for test value
byte[] iv = new byte[8]; new SecureRandom().nextBytes(iv); // maybe SIV instead?
MessageDigest pbh = MessageDigest.getInstance("MD5");
byte[] derive = new byte[32]; // round up to multiple of pbh.getDigestLength()=16
for(int off = 0; off < derive.length; off += 16 ){
if( off>0 ) pbh.update(derive,off-16,16);
pbh.update(passphrase); pbh.update(iv);
pbh.digest(derive, off, 16);
}
Cipher pbc = Cipher.getInstance("DESede/CBC/PKCS5Padding");
pbc.init (Cipher.ENCRYPT_MODE, new SecretKeySpec(derive,0,24,"DESede"), new IvParameterSpec(iv));
byte[] enc = pbc.doFinal(old);
// write to PEM format (substitute other file if desired)
System.out.println ("-----BEGIN RSA PRIVATE KEY-----");
System.out.println ("Proc-Type: 4,ENCRYPTED");
System.out.println ("DEK-Info: DES-EDE3-CBC," + DatatypeConverter.printHexBinary(iv));
System.out.println (); // empty line
String b64 = Base64.getEncoder().encodeToString(enc);
for( int off = 0; off < b64.length(); off += 64 )
System.out.println (b64.substring(off, off+64<b64.length()?off+64:b64.length()));
System.out.println ("-----END RSA PRIVATE KEY-----");
Finally, OpenSSL format requires the encryption IV and the PBKDF salt be the same, and it makes that value random, so I did also. The computed value you used for salt only, MD5(password||data), vaguely resembles the synthetic-IV (SIV) construction that is now accepted for use with encryption, but it is not the same, plus I don't know if any competent analyst has considered the case where SIV is also used for PBKDF salt, so I would be reluctant to rely on this technique here. If you want to ask about that point, it's not really a programming Q and would be more suitable on cryptography.SX or maybe security.SX.
added for comments:
That code's output works for me with puttygen from 0.70, both on Windows (from upstream=chiark) and on CentOS6 (from EPEL). According to the source, the error message you gave occurs only if cmdgen has called key_type in sshpubk.c which recognized the first line as beginning with "-----BEGIN " but not "-----BEGIN OPENSSH PRIVATE KEY" (which is a very different format), then via import_ssh2 and openssh_pem_read called load_openssh_pem_key in import.c which does NOT find the first line beginning with "-----BEGIN " and ending with "PRIVATE KEY-----". This is very weird because both of those PLUS "RSA " in between is generated by my code and is needed for OpenSSH (or openssl) to accept it. Try looking at every byte of the first line at least (maybe first two lines) with something like cat -vet or sed -n l or in a pinch od -c.
RFC 2898 is rather old now; good practice today is usually 10s of thousands to 100s of thousands of iterations, and better practice is not to use an iterated hash at all but instead something memory-hard like scrypt or Argon2. But as I already wrote, OpenSSL legacy PEM encryption, which was designed back in the 1990s, uses ONE (un, eine, 1) iteration and therefore is a POOR and INSECURE scheme. Nobody can change it now because that's how it was designed. If you want decent PBE, don't use this format.
If you need a key only for SSH: OpenSSH (for several years now) supports and recent versions of Putty(gen) can import the OpenSSH-defined 'new format', which uses bcrypt, but jsch can't. OpenSSH (using OpenSSL) can also read (PEM) PKCS8 which allows PBKDF2 (better though not best) with iterations as desired, and it looks like jsch can, but not Putty(gen). I don't know for Cyberduck or other implementations.
mister
I think that before invoking encrypt you need to decrypt two times more for security reasons. Instead of salt use also pepper salt and pepper. Do not mix algorithm with aes256.
Kind regards, rajeesh

C++/Openssl Get RSA key from encoded bytes (encoded by java)

Does somebody know how I can create an RSA key in C++ from an encoded byte array?
My problem is that I try to develop a C++ client that is interacting with a server which is coded in Java.
Well in Java the client receives the rsa key encoded as an byte array, decodes it to a RSA RSAPublicKey and encrypts a message with this key.
The java server/client code:
public static PublicKey decodePublicKey(byte[] p_75896_0_)
{
try
{
X509EncodedKeySpec var1 = new X509EncodedKeySpec(p_75896_0_);
KeyFactory var2 = KeyFactory.getInstance("RSA");
return var2.generatePublic(var1);
}
catch (NoSuchAlgorithmException var3)
{
;
}
catch (InvalidKeySpecException var4)
{
;
}
field_180198_a.error("Public key reconstitute failed!");
return null;
}
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
this.publicKey = CryptManager.decodePublicKey(data.readByteArray());
After that the client is doing some encrypting stuff with his key.
The key gets sent like this:
public static final KeyPair keys;
static
{
try
{
KeyPairGenerator generator = KeyPairGenerator.getInstance( "RSA" );
generator.initialize( 1024 );
keys = generator.generateKeyPair();
} catch ( NoSuchAlgorithmException ex )
{
throw new ExceptionInInitializerError( ex );
}
}
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
byte[] pubKey = keys.getPublic().getEncoded();
writeBytes(pubKey);
My problem is how to get the key from the byte array in C++.
Update:
Im currently working on this code:
char* publicKey = ...
int publicKeyLength = 162;
EVP_PKEY* key = EVP_PKEY_new();
if(d2i_PUBKEY(&key, (const unsigned char**) &publicKey, publicKeyLength) != 0){
logError("Problem!");
}
logMessage("Key: "+to_string((uint64_t) (void*) key));
Well my problem now is that i have an SIGSEGV error on the third line and dont know what this course. Well the key should be valid.
What Java returns for the public key is a SubjectPublicKeyInfo structure, which doesn't just contain the (PKCS#1 encoded) values for the public key, but also the key identifier etc.
So to decode this you have to type "decode SubjectPublicKeyInfo openssl" in your favorite search engine. Then you'll find (after some scrolling) the following information from here:
d2i_PUBKEY() and i2d_PUBKEY() decode and encode an EVP_PKEY structure
using SubjectPublicKeyInfo format. They otherwise follow the conventions
of other ASN.1 functions such as d2i_X509().
Obviously you'd need the decoding algorithm.
Note that openssl is C so beware of buffer overruns when decoding stuff. I'd rather have a 1024 bit RSA key that is used with secure software than a 2048 bit key with software full of buffer overruns.
Needless to say you need to trust the public key before importing it. There is a reason why it is called the public key infrastructure (PKI).

Decrypt a data by 3DES-128 bits CBC Mode (padding zero)) using java

In the process of decrypting an encrypted data, i am having a little challenge with it. I will be so glad if anyone can help putting me through.
Although, i have already studied the algorithm in carrying out this operation, since i am to get my data from a device, which already has its Base Derivation Key(BDK), Initially loaded key serial number and the initially loaded pin entry device key.
In the documentation that was been given, we have the Initially loaded key serial number, data encryption key variant and the track 2 Data (which is in plaintext).
In this example, i was made to know that they actually used a 3DES-128 bits CBC Mode (padding zero) method.
my question now is, how was the plaintext gotten from the encrypted data. I will be so glad, if anyone can put me through (stating the flow or the algorithm to use in decrypting this data).
Will so much appreciate your time.
Since you want to try to carrying out the 3DES-128 bits CBC Mode (padding zero) method, try out this git https://github.com/meshileya/dukpt to use in getting the plaintext from the ciphertext.
Since you already have your BDK and KSN, just try to run the method below.
public static void main(String[] args) {
try {
String theksn = "This should be your KSN";
String encrypted = "This should be the encrypted data";
String BDK = "The BDK you mentioned up there";
tracking= DukptDecrypt.decrypt(theksn, BDK, encrypted);
System.out.print("PlainText"+ tracking);
}catch (Exception e){System.out.print(e);}
}
One of the more stupid things about the Oracle implementation is that the SecretKeyFactory does not support DES ABA keys, also known as "two-key" DES keys.
These keys for triple-DES operation consists of a single DES key A, followed by single DES key B. Key A is used both for the first and last iteration of DES within DES EDE (Encrypt-Decrypt-Encrypt).
If you stay within software you can create a method to create such keys. The problem is that the resulting keys actually have 192 bits, which is simply not correct - it makes it impossible to distinguish between the key sizes.
Anyway, the following code can be used to generate DES ABA keys:
private static final int DES_KEY_SIZE_BYTES = 64 / Byte.SIZE;
private static final int DES_ABA_KEY_SIZE_BYTES = 2 * DES_KEY_SIZE_BYTES;
private static final int DES_ABC_KEY_SIZE_BYTES = 3 * DES_KEY_SIZE_BYTES;
public static SecretKey createDES_ABAKey(byte[] key) {
if (key.length != DES_ABA_KEY_SIZE_BYTES) {
throw new IllegalArgumentException("128 bit key argument with size expected (including parity bits.)");
}
try {
byte[] desABCKey = new byte[DES_ABC_KEY_SIZE_BYTES];
System.arraycopy(key, 0, desABCKey, 0, DES_ABA_KEY_SIZE_BYTES);
System.arraycopy(key, 0, desABCKey, DES_ABA_KEY_SIZE_BYTES, DES_KEY_SIZE_BYTES);
SecretKeySpec spec = new SecretKeySpec(desABCKey, "DESede");
SecretKeyFactory factory = SecretKeyFactory.getInstance("DESede");
SecretKey desKey = factory.generateSecret(spec);
return desKey;
} catch (GeneralSecurityException e) {
throw new RuntimeException("DES-EDE ABC key factory not functioning correctly", e);
}
}
OK, so that leaves us with the CBC encryption (no padding, and zero IV):
private static final byte[] ENCRYPTION_KEY = Hex.decode("448D3F076D8304036A55A3D7E0055A78");
private static final byte[] PLAINTEXT = Hex.decode("1234567890ABCDEFFEDCBA0987654321");
public static void main(String[] args) throws Exception {
SecretKey desABAKey = createDES_ABAKey(ENCRYPTION_KEY);
Cipher desEDE = Cipher.getInstance("DESede/CBC/NoPadding");
IvParameterSpec zeroIV = new IvParameterSpec(new byte[desEDE.getBlockSize()]);
desEDE.init(Cipher.ENCRYPT_MODE, desABAKey, zeroIV);
byte[] ciphertext = desEDE.doFinal(PLAINTEXT);
System.out.println(Hex.toHexString(ciphertext));
}
I've used the Bouncy Castle hexadecimal codec, but other hexadecimal codecs can also be used.

DH Key Agreement in Java: why generated keys are mostly equal for both parties?

I'm trying to create a very simple key exchange in Java. After the code and output there are questions:
public class Blergh {
public static KeyPair genKeyPair512() {
try {
AlgorithmParameterGenerator paramGen = AlgorithmParameterGenerator
.getInstance("DH");
paramGen.init(512);
BigInteger g = new BigInteger(
"7961C6D7913FDF8A034593294FA52D6F8354E9EDFE3EDC8EF082D36662D69DFE8CA7DC7480121C98B9774DFF915FB710D79E1BCBA68C0D429CD6B9AD73C0EF20",
16);
BigInteger p = new BigInteger(
"00AC86AB9A1F921B251027BD10B93D0A8D9A260364974648E2543E8CD5C48DB4FFBEF0C3843465BA8DE20FFA36FFAF840B8CF26C9EB865BA184642A5F84606AEC5",
16);
final KeyPairGenerator keyGen = KeyPairGenerator.getInstance("DH");
final DHParameterSpec dhSpec = new DHParameterSpec(p, g, 511);
keyGen.initialize(dhSpec);
return keyGen.generateKeyPair();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (InvalidAlgorithmParameterException e) {
e.printStackTrace();
}
return null;
}
public static byte[] genSharedSecretKey(KeyPair keyPair,
byte[] bytesPeerPublicKey) {
PrivateKey privateKey = keyPair.getPrivate();
try {
X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(
bytesPeerPublicKey);
KeyFactory keyFact = KeyFactory.getInstance("DH");
PublicKey peerPublicKey = keyFact.generatePublic(x509KeySpec);
KeyAgreement ka;
ka = KeyAgreement.getInstance("DH");
ka.init(privateKey);
ka.doPhase(peerPublicKey, true);
String algorithm = "AES";
SecretKey secretKey = ka.generateSecret(algorithm);
return secretKey.getEncoded();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (InvalidKeyException e) {
e.printStackTrace();
} catch (InvalidKeySpecException e) {
e.printStackTrace();
}
return null;
}
public static void main(String argv[]) {
KeyPair akp = genKeyPair512();
KeyPair bkp = genKeyPair512();
System.out.println("Ali pub key: "
+ toRawHex(akp.getPublic().getEncoded()));
System.out.println("Bob pub key: "
+ toRawHex(bkp.getPublic().getEncoded()));
System.out.println("Ali pri key: "
+ toRawHex(akp.getPrivate().getEncoded()));
System.out.println("Bob pri key: "
+ toRawHex(bkp.getPrivate().getEncoded()));
byte[] apk = akp.getPublic().getEncoded();
byte[] bpk = bkp.getPublic().getEncoded();
byte[] as = genSharedSecretKey(akp, bpk);
byte[] bs = genSharedSecretKey(bkp, apk);
}
}
It generates, for example, the output:
Ali pub key: 3081DF30819706092A864886F70D010301308189024100AC86AB9A1F921B251027BD10B93D0A8D9A260364974648E2543E8CD5C48DB4FFBEF0C3843465BA8DE20FFA36FFAF840B8CF26C9EB865BA184642A5F84606AEC502407961C6D7913FDF8A034593294FA52D6F8354E9EDFE3EDC8EF082D36662D69DFE8CA7DC7480121C98B9774DFF915FB710D79E1BCBA68C0D429CD6B9AD73C0EF20020201FF03430002403BBCBF4052CD1CEF7A580A919AF75186CE0A624BC93AA47922C3822CE60A8CD10CE98550ABCA2D39DA2F09903C3D761B9A1C4AED185934FE5D08AD0CD097AA86
Bob pub key: 3081DF30819706092A864886F70D010301308189024100AC86AB9A1F921B251027BD10B93D0A8D9A260364974648E2543E8CD5C48DB4FFBEF0C3843465BA8DE20FFA36FFAF840B8CF26C9EB865BA184642A5F84606AEC502407961C6D7913FDF8A034593294FA52D6F8354E9EDFE3EDC8EF082D36662D69DFE8CA7DC7480121C98B9774DFF915FB710D79E1BCBA68C0D429CD6B9AD73C0EF20020201FF03430002400F119BC06E53F8C33D3F7C16473D1F9E001FABF4D619930C34945AA2C6D0A00CB9B332CEAF2C0C2FB61D3F568B9263B69A152410237F4D793F8B571C34AB37B7
Ali pri key: 3081E102010030819706092A864886F70D010301308189024100AC86AB9A1F921B251027BD10B93D0A8D9A260364974648E2543E8CD5C48DB4FFBEF0C3843465BA8DE20FFA36FFAF840B8CF26C9EB865BA184642A5F84606AEC502407961C6D7913FDF8A034593294FA52D6F8354E9EDFE3EDC8EF082D36662D69DFE8CA7DC7480121C98B9774DFF915FB710D79E1BCBA68C0D429CD6B9AD73C0EF20020201FF0442024043BA0B3C73EB7482B80DE98FA81A7E50B0DC2F5786CA62285655BD36CE012C056545DE5EED65736D9135EC9CD5148F8D68FF3C7B5CC62B2A1F7649698B26D1BE
Bob pri key: 3081E102010030819706092A864886F70D010301308189024100AC86AB9A1F921B251027BD10B93D0A8D9A260364974648E2543E8CD5C48DB4FFBEF0C3843465BA8DE20FFA36FFAF840B8CF26C9EB865BA184642A5F84606AEC502407961C6D7913FDF8A034593294FA52D6F8354E9EDFE3EDC8EF082D36662D69DFE8CA7DC7480121C98B9774DFF915FB710D79E1BCBA68C0D429CD6B9AD73C0EF20020201FF04420240485DDD7F5BDECA92FEE30D9D15211D274BC0FF7838B8B51E7894263CA65DB4E394033CE3E2146C0CD0CA74E2DB0EF95D01EE0DC4011A3EC6A8EC61CC2FDC5A44
So, I have a main question and two additional ones:
Why more than half of the bytes of the keys (both private and public) between Alice and Bob are equal? For instance, Alice's private starts with 3081E102010030819706092A... and Bob's private with 3081E102010030819706092A... as well.
Why is DHParameterSpec created with 511 and not 512 (at least in most examples on the web)?
Assuming there's nothing wrong with the generated keys, is there something else that I'm missing here, or this code should be safe when adpated to exchange the public keys through the internet?
Any help is welcome. Thanks in advance.
EDIT: The third question applies to the genSharedSecretKey() as well (that is, the whole code), though I'm not calling/showing output because it would be irrelevant.
Why more than half of the bytes of the keys (both private and public) between Alice and Bob are equal? For instance, Alice's private starts with 3081E102010030819706092A... and Bob's private with 3081E102010030819706092A... as well.
When you call getEncoded() you receive a copy of the key data in ASN.1 format. Depending upon the objects being represented, there is often a certain amount of duplicate data present in any two structures. Somewhere within the structure will be the key data, which will be different for Alice and Bob.
Indeed, as CodesInChaos stated in the comments, the first part of the ASN.1 structure contains the group parameter which is identical between both parties.
The reason why you receive keys with much duplicate that is - as Duncan suggested - that it is encoded in an ASN.1 format. And that does explain the amount of duplicate data as the contains the parameters p and g you've specified yourself - look for the values in the hexadecimal string. If you want to take a look at the contents, simply paste the hexadecimal output into an online ASN.1 decoder, such as the one found here.
There is a lot to be said about DH and (key) sizes. Some interesting discussions can be found on security.stackexchange.com and crypto.stackexchange.com. But in the end, we don't know; you typed in the code, so we should ask you how and why the Diffie-Hellman parameters were chosen.
As for the third question; to distribute public keys on the internet you need to establish trust. You need to trust the other party. Diffie Hellman key agreement by itself does not establish trust; only a shared secret with some other party. So you need a secure protocol (such as TLS) with trusted certificates or keys that contains an authentication component. A lot of knowledge is required to create such a protocol however; if you do not fully understand the code you just showed us, you will fail to create such a protocol yourself.
Note that if both parties have agreed upon a set of parameters, that it may not be necessary to send the whole encoded public key; you may just have to send the values that the other party does not know so the other party can reconstruct the public key.

Categories

Resources