4040import net .i2p .crypto .eddsa .spec .EdDSAPrivateKeySpec ;
4141import net .i2p .crypto .eddsa .spec .EdDSAPublicKeySpec ;
4242
43+ /**
44+ * A wrapper class which makes it easy to generate and use Ed25519 private keys.
45+ * All methods are using java's {@link SecureRandom} PRNG, if a PRNG is needed.
46+ * All sign methods are using the {@link HashCondenser} for efficiency.
47+ *
48+ * @author DevCybran
49+ *
50+ */
4351public class Ed25519PrivateKey implements Destroyable {
4452 static final EdDSAParameterSpec P_SPEC = EdDSANamedCurveTable .getByName (EdDSANamedCurveTable .CURVE_ED25519_SHA512 );
4553 private static SecureRandom random = null ;
@@ -70,11 +78,34 @@ static byte[] hash(byte[] data) throws NoSuchAlgorithmException {
7078 return md .digest (data );
7179 }
7280
81+ /**
82+ * Loads a private key from the specified file.
83+ *
84+ * @param privateKeyFile the file to read the private key from
85+ * @param password the password which was passed to {@link #saveAsFile(File, char[])} upon saving the file
86+ * @return the read private key
87+ * @throws IOException if an IO error occurs while reading the file
88+ * @throws IllegalArgumentException if password is null
89+ * @throws NoSuchAlgorithmException if either of the encryption algorithms is not present on this machine
90+ * @throws NoSuchPaddingException if the PKCS5 padding is not present on this machine
91+ * @throws InvalidKeyException if the passed privatKeyString has an invalid format
92+ */
7393 public static Ed25519PrivateKey loadFromFile (File privateKeyFile , char [] password ) throws IllegalArgumentException , IOException , NoSuchAlgorithmException , NoSuchPaddingException , InvalidKeyException {
7494 String key = new String (Files .readAllBytes (privateKeyFile .toPath ()), StandardCharsets .UTF_8 );
7595 return loadFromString (key , password );
7696 }
7797
98+ /**
99+ * Decodes a private key from the specified string.
100+ *
101+ * @param privateKeyString A hexadecimal encoded representation of the key, generated by {@link #saveAsString(char[])}
102+ * @param password the password which was passed to {@link #saveAsString(char[])} upon generating the privateKeyString
103+ * @return the decoded private key
104+ * @throws IllegalArgumentException if password is null
105+ * @throws NoSuchAlgorithmException if either of the encryption algorithms is not present on this machine
106+ * @throws NoSuchPaddingException if the PKCS5 padding is not present on this machine
107+ * @throws InvalidKeyException if the passed privatKeyString has an invalid format
108+ */
78109 public static Ed25519PrivateKey loadFromString (String privateKeyString , char [] password ) throws IllegalArgumentException , NoSuchAlgorithmException , NoSuchPaddingException , InvalidKeyException {
79110 if (privateKeyString .length () < (512 +128 +256 +512 )/8 *2 ) throw new InvalidKeyException ("the supplied key is not a valid private key" ); // salt + iv + key + hash
80111 byte [] salt , iv , encryptedKey ;
@@ -110,6 +141,13 @@ public static Ed25519PrivateKey loadFromString(String privateKeyString, char[] p
110141 }
111142 }
112143
144+ /**
145+ * Generates a new private key.
146+ * {@link SecureRandom} is used as a source to seed this key.
147+ *
148+ *
149+ * @return A new private key
150+ */
113151 public static Ed25519PrivateKey generate () {
114152 KeyPairGenerator gen = new KeyPairGenerator ();
115153 try {
@@ -126,11 +164,33 @@ private Ed25519PrivateKey(EdDSAPrivateKey key, Ed25519PublicKey pubKey) {
126164 this .pubKey = pubKey ;
127165 }
128166
167+ /**
168+ * Encrypts, encodes and saves this key to the specified file.
169+ *
170+ * @see #saveAsString(char[])
171+ * @param privateKeyFile the file to save the private key to
172+ * @param password a password for encrypting the private key. The longer, the better.
173+ * @throws IllegalArgumentException if password is null
174+ * @throws IOException if an IO error occurs when writing the file
175+ * @throws NoSuchAlgorithmException if either of the encryption algorithms is not present on this machine
176+ * @throws NoSuchPaddingException if the PKCS5 padding is not present on this machine
177+ */
129178 public void saveAsFile (File privateKeyFile , char [] password ) throws IllegalArgumentException , IOException , NoSuchAlgorithmException , NoSuchPaddingException {
130179 String key = this .saveAsString (password );
131180 Files .write (privateKeyFile .toPath (), key .getBytes (StandardCharsets .UTF_8 ));
132181 }
133182
183+ /**
184+ * Encodes this key as a hexadecimal String.
185+ * A password is used to encrypt this key. The password and a generated 512-bit long salt are fed to 1 million iterations of PBKDF2 with SHA-512 to generate a secret key.
186+ * The secret key is used to encrypt the private key using AES-256-CBC-PKCS5.
187+ *
188+ * @param password a password for encrypting the private key. The longer, the better.
189+ * @return a hexadecimal encoded and encrypted representation of this private key
190+ * @throws IllegalArgumentException if password is null
191+ * @throws NoSuchAlgorithmException if either of the encryption algorithms is not present on this machine
192+ * @throws NoSuchPaddingException if the PKCS5 padding is not present on this machine
193+ */
134194 public String saveAsString (char [] password ) throws IllegalArgumentException , NoSuchAlgorithmException , NoSuchPaddingException {
135195 byte [] salt = new byte [512 /8 ];
136196 random ().nextBytes (salt );
@@ -158,6 +218,11 @@ public String saveAsString(char[] password) throws IllegalArgumentException, NoS
158218 }
159219 }
160220
221+ /**
222+ * Creates (if necessary) and returns the public key for this private key.
223+ *
224+ * @return the public key for this private key
225+ */
161226 public Ed25519PublicKey derivePublicKey () {
162227 if (this .pubKey ==null ) {
163228 this .pubKey = new Ed25519PublicKey (new EdDSAPublicKey (new EdDSAPublicKeySpec (this .key .getA (), this .key .getParams ())));
@@ -178,18 +243,55 @@ private String signLow(byte[] data) {
178243 }
179244 }
180245
246+ /**
247+ * Signs the given byte array.
248+ * {@link HashCondenser#compute(byte[])} with default settings is preprocessing the array before signing it.
249+ *
250+ * @param data
251+ * @return a hexadecimal representation of the signature
252+ * @throws NoSuchAlgorithmException if the hash algorithm is not present on this machine
253+ */
181254 public String sign (byte [] data ) throws NoSuchAlgorithmException {
182255 return signLow (HashCondenser .getInstance ().compute (data ));
183256 }
184257
258+ /**
259+ * Signs the given String by converting it to bytes using the UTF-8 charset.
260+ *
261+ * @see #sign(byte[])
262+ * @param data
263+ * @return a hexadecimal representation of the signature
264+ * @throws NoSuchAlgorithmException if the hash algorithm is not present on this machine
265+ */
185266 public String sign (String data ) throws NoSuchAlgorithmException {
186267 return this .sign (data .getBytes (StandardCharsets .UTF_8 ));
187268 }
188269
270+ /**
271+ * Signs all data which can be read from the given InputStream.
272+ * {@link HashCondenser#compute(byte[])} with default settings is preprocessing the stream before signing it, which will allow signing huge files.
273+ *
274+ * @param source the data source
275+ * @param sourceSize the exact size of all data which will pass through the InputStream
276+ * @return a hexadecimal representation of the signature
277+ * @throws IllegalArgumentException if sourceSize is not the correct size
278+ * @throws NoSuchAlgorithmException if the hash algorithm is not present on this machine
279+ * @throws IOException if an IO error occurs while reading the stream
280+ */
189281 public String sign (InputStream source , long sourceSize ) throws IllegalArgumentException , NoSuchAlgorithmException , IOException {
190282 return signLow (HashCondenser .getInstance ().compute (source , sourceSize ));
191283 }
192284
285+ /**
286+ * Signs the content of the given file.
287+ *
288+ * @see #sign(InputStream, long)
289+ * @param source
290+ * @return a hexadecimal representation of the signature
291+ * @throws IOException if an IO error occurs while reading the file
292+ * @throws IllegalArgumentException if the file's size changes during computation
293+ * @throws NoSuchAlgorithmException if the hash algorithm is not present on this machine
294+ */
193295 public String sign (File source ) throws IOException , IllegalArgumentException , NoSuchAlgorithmException {
194296 if (!source .isFile ()) throw new FileNotFoundException (source .getAbsolutePath ());
195297 long fileSize = source .length ();
@@ -198,10 +300,30 @@ public String sign(File source) throws IOException, IllegalArgumentException, No
198300 }
199301 }
200302
303+ /**
304+ * Signs the content of the given file and writes the signature to a file of the same name which has a ".sig" extension appended
305+ * Example: If you sign "MyFile.dat", the signature will be written to "MyFile.dat.sig"
306+ *
307+ * @see #signToFile(File, File)
308+ * @param source
309+ * @throws IOException if an IO error occurs while reading the file
310+ * @throws IllegalArgumentException if the file's size changes during computation
311+ * @throws NoSuchAlgorithmException if the hash algorithm is not present on this machine
312+ */
201313 public void signToFile (File source ) throws IOException , IllegalArgumentException , NoSuchAlgorithmException {
202314 this .signToFile (source , new File (source , ".sig" ));
203315 }
204316
317+ /**
318+ * Signs the content of the given source file and writes the signature to the given signature file.
319+ *
320+ * @see #sign(File)
321+ * @param source
322+ * @param signatureFile the file to write the signature to
323+ * @throws IOException if an IO error occurs while reading the file
324+ * @throws IllegalArgumentException if the file's size changes during computation
325+ * @throws NoSuchAlgorithmException if the hash algorithm is not present on this machine
326+ */
205327 public void signToFile (File source , File signatureFile ) throws IOException , IllegalArgumentException , NoSuchAlgorithmException {
206328 String signature = this .sign (source );
207329 Files .write (signatureFile .toPath (), signature .getBytes (StandardCharsets .UTF_8 ));
0 commit comments