From 6a9e2986bdf2349e44a197a263280dc2443bc0e2 Mon Sep 17 00:00:00 2001 From: Justin Ludwig Date: Fri, 13 Apr 2018 16:58:16 -0700 Subject: [PATCH] allow use of private keys with no passphrase #12 Add Key.NO_PASSPHRASE constant to signal that a key doesn't use a passphrase (as well as Key/Subkey.noPassphrase property), and add unit test to validate a passphrase-less key works for signing and decryption. --- src/main/java/org/c02e/jpgpj/Decryptor.java | 2 +- src/main/java/org/c02e/jpgpj/Key.java | 12 +++++++ src/main/java/org/c02e/jpgpj/Subkey.java | 24 ++++++++++++- .../org/c02e/jpgpj/EncryptorSpec.groovy | 15 ++++++++ src/test/resources/test-key-no-passphrase.asc | 34 +++++++++++++++++++ 5 files changed, 85 insertions(+), 2 deletions(-) create mode 100644 src/test/resources/test-key-no-passphrase.asc diff --git a/src/main/java/org/c02e/jpgpj/Decryptor.java b/src/main/java/org/c02e/jpgpj/Decryptor.java index 11cc7fa..f6cef57 100644 --- a/src/main/java/org/c02e/jpgpj/Decryptor.java +++ b/src/main/java/org/c02e/jpgpj/Decryptor.java @@ -510,7 +510,7 @@ protected PGPContentVerifierBuilderProvider getVerifierProvider() { } /** - * Builds a symmetric-encryption decryptor for the specified passphrase. + * Builds a symmetric-encryption decryptor for the specified subkey. */ protected PublicKeyDataDecryptorFactory buildPublicKeyDecryptor( Subkey subkey) throws PGPException { diff --git a/src/main/java/org/c02e/jpgpj/Key.java b/src/main/java/org/c02e/jpgpj/Key.java index caeefae..35b11eb 100644 --- a/src/main/java/org/c02e/jpgpj/Key.java +++ b/src/main/java/org/c02e/jpgpj/Key.java @@ -54,6 +54,9 @@ * or all user IDs, as the message signer). */ public class Key { + /** Use this value to set the passphrase of a passphrase-less key. */ + public static String NO_PASSPHRASE = "JPGPJ_NO_PASSPHRASE"; + protected String signingUid; protected List subkeys; @@ -164,6 +167,15 @@ public void setPassphrase(String x) { subkey.setPassphrase(x); } + /** + * True to flag all subkeys as needing no passphrase to unlock; + * false to require a passphrase to be (re-)set on all subkeys. + */ + public void setNoPassphrase(boolean x) { + for (Subkey subkey : subkeys) + subkey.setNoPassphrase(x); + } + /** * User ID strings for master subkey * (ex ["My Name (comment) <me@example.com>"]). diff --git a/src/main/java/org/c02e/jpgpj/Subkey.java b/src/main/java/org/c02e/jpgpj/Subkey.java index d947c2d..5be6875 100644 --- a/src/main/java/org/c02e/jpgpj/Subkey.java +++ b/src/main/java/org/c02e/jpgpj/Subkey.java @@ -47,6 +47,8 @@ * you must also set the subkey's passphrase, either via the * {@link #setPassphrase} method on the subkey, or the * {@link Key#setPassphrase} on its containing {@link Key}. + * If the subkey does not have a passphrase, set the passphrase to the + * {@link Key#NO_PASSPHRASE} constant. */ public class Subkey { protected boolean forSigning; @@ -139,6 +141,8 @@ public void setForDecryption(boolean x) { /** * Passphrase needed to unlock the private part * of the subkey's public key-pair; or empty string. + * Use {@link Key#NO_PASSPHRASE} to signal the private part of the subkey + * is not protected by a passphrase. */ public String getPassphrase() { return passphrase; @@ -147,11 +151,29 @@ public String getPassphrase() { /** * Passphrase needed to unlock the private part * of the subkey's public key-pair; or empty string. + * Use {@link Key#NO_PASSPHRASE} to signal the private part of the subkey + * is not protected by a passphrase. */ public void setPassphrase(String x) { passphrase = x != null ? x : ""; } + /** + * True if no passphrase is needed to unlock the private part + * of the subkey's public key-pair. + */ + public boolean isNoPassphrase() { + return Key.NO_PASSPHRASE.equals(passphrase); + } + + /** + * True if no passphrase is needed to unlock the private part + * of the subkey's public key-pair. + */ + public void setNoPassphrase(boolean x) { + passphrase = x ? Key.NO_PASSPHRASE : ""; + } + /** * Bouncy castle public-key pair, * containing only the public part of the pair; or null. @@ -323,7 +345,7 @@ protected PGPPrivateKey extractPrivateKey(String passphrase) * Builds a secret key decryptor for the specified passphrase. */ protected PBESecretKeyDecryptor buildDecryptor(String passphrase) { - char[] chars = !Util.isEmpty(passphrase) ? + char[] chars = !Util.isEmpty(passphrase) && !isNoPassphrase() ? passphrase.toCharArray() : new char[0]; return new BcPBESecretKeyDecryptorBuilder( new BcPGPDigestCalculatorProvider()).build(chars); diff --git a/src/test/groovy/org/c02e/jpgpj/EncryptorSpec.groovy b/src/test/groovy/org/c02e/jpgpj/EncryptorSpec.groovy index 4388cb7..1dadebf 100644 --- a/src/test/groovy/org/c02e/jpgpj/EncryptorSpec.groovy +++ b/src/test/groovy/org/c02e/jpgpj/EncryptorSpec.groovy @@ -514,6 +514,21 @@ hQEMAyne546XDHBhAQ... meta.verified.keys.signingUid == ['Test Key 2 '] } + def "encrypt and sign with passphrase-less key"() { + when: + def encryptor = new Encryptor(new Ring(stream('test-key-no-passphrase.asc'))) + encryptor.ring.keys*.noPassphrase = true + encryptor.encrypt plainIn, cipherOut + + def decryptor = new Decryptor(new Ring(stream('test-key-no-passphrase.asc'))) + decryptor.ring.keys*.noPassphrase = true + def meta = decryptor.decrypt(cipherIn, plainOut) + + then: + plainOut.toString() == plainText + meta.verified + } + def "best packet size"() { when: def encryptor = new Encryptor(); diff --git a/src/test/resources/test-key-no-passphrase.asc b/src/test/resources/test-key-no-passphrase.asc new file mode 100644 index 0000000..72da16d --- /dev/null +++ b/src/test/resources/test-key-no-passphrase.asc @@ -0,0 +1,34 @@ +-----BEGIN PGP PRIVATE KEY BLOCK----- +Version: GnuPG v1 + +lgAAAdgEWtE3cAEEANMVepmoansMHh6//w+PeRETdf/k/1zTcHZxqVPpsNLpeHDS +5QJGXxNeGDbozPvu0obG20Qm1D26+CrbhhPHQr/3rPmlQn12SPbjoHkA8/jewcMM +1ABejWf4X6RVYY0xbA6uYU56+a2KkUU4pVFwL2EiuclS0ibjApHFKOYDMIZpABEB +AAEAA/4k7EAoki7kDLPVFakQR/y5qbGNnDk1+89TjKC9qqZRUmEMvTstWXFmkiJC +spiupfEh/bVbl74u+R5RYtpyabvJRr6r0TbyKz17mzF0Z512vryo5yvAkTyuJjoH +5t8/wFk+ENcT9JxBnmr7jJ/R7cyK22J1RJgy9pyf1riLM0Gm7QIA59UxNyypOBh6 +7pFADFMDt9RNZI+gUizt7yqbFW4g3dd+ZL8/tXoUrxAXwar+ofpj7NJ6/pNMR4xB +qXhtDA8/9QIA6RaSS8dQoSWhCjvVI4y2PYuyH3qj8mCz2oq/CLYMashbeOv+2Fde +J+RZ+WJXXpzcBybOnYxyvNWS189+DkYoJQH/RMgc1RR+UP3mjzyOM7U/DLZvia5n +A7icnQmBLS3Nw3H0SOG3Q16kLUdNH9Pcq8/jcSJFUfBPcKo3rrRAb6WrIaOytC9O +byBQYXNzcGhyYXNlIDx0ZXN0LWtleS1uby1wYXNzcGhyYXNlQGMwMmUub3JnPoi3 +BBMBCAAhBQJa0TdwAhsDBQsJCAcCBhUICQoLAgQWAgMBAh4BAheAAAoJEPQrNY6Y +hh8X+EYEALFpXh22otAoZ5XVsZP7/me0Xi7bcj2xN+/f7wwXXQ0Js22P2s8koR5U +RewLgHHpxbOFmIqL76yda5OJZtsZwOH1alU0CeFviK3m47Htypyx0AUMBEN/1gWC +nKxl0rM+2ofAwAxzrEf0xbjP7/4zt0CUK66nj37Xd/eiJKxPzYIfngAAAdgEWtE3 +cAEEAKfRvYix24rYhrVWrTUeaPL74vUFFMOuXIOYJ5463cTKZeQ4ecn072ZtbC/n +hHeV5RGkUu/5GXORR8hPP53/dvm+0fhAP7NfbSMlpfwY1fpoQUVNsVH8CTjNJrSO +AMtS4E/tHIFhhfCN9GV9U8a5/k8uwSTWzpMTdRmktXoa78SzABEBAAEAA/4vElst +8OlqMCLU0Xui9yUr5rA51tOEsa8ccDzImq1iyfy7f4XVuoC+A7pBU5ip7F8EzElB +K2dSrbDrRCNNf38QyQTy7Ugz8QXEOj4BawvpG41jqWn47+Wr6iyiaFz1slXrCXaH +zq/kB8HGcgwjujLk2Vj+1pHFXQ1zDxTlFR3RKQIAy/Kqd+bWziI+7WFgbV5YJsoO +KDNvvbGjc+2xNuUWJ3Yf1suoU90VuSpb0Rx2RnoxmOMDlGawix6nQb12NyTWOwIA +0qaKgUXCMepboXFrr6BviWUfR49+4Zr/B8KcUElER2tBRhwiEqR3ClMXLT3weTea +l2UQ7EbzPVcbVTst7RjL6QH+LKWBoAwI+IVTELA/YpW5Wf8Z2WkPQjYOTqa3sl21 +DIDwvUVOguMYAfRo5nFvsU+a7nVe1A6We+S3KZrN/eqYK54ViJ8EGAEIAAkFAlrR +N3ACGwwACgkQ9Cs1jpiGHxcJQgQAvr3BjuacHcFWRzAigi+FILRIvSJk1RrC7S9d +cu1N79GQXeOBRckUja50YkSPQ1IpAR7RV44xeeWDIS0aSNMz6W4snxfAlIsifARf +KZ4ioj0iMMhf83CeuB0Yp46iRStBMPb1EvVeBXBIokLBOKQZq+RJTeMNUwN06fkx +l8nD0Kg= +=Py45 +-----END PGP PRIVATE KEY BLOCK-----