net.freeutils.util.Crypto Maven / Gradle / Ivy
Show all versions of jelementary Show documentation
/*
* Copyright © 2003-2024 Amichai Rothman
*
* This file is part of JElementary - the Java Elementary Utilities package.
*
* JElementary is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* JElementary is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with JElementary. If not, see .
*
* For additional info see https://www.freeutils.net/source/jelementary/
*/
package net.freeutils.util;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import java.io.*;
import java.math.BigInteger;
import java.security.*;
import java.security.spec.*;
import java.util.Arrays;
/**
* The {@code Crypto} class contains cryptography-related utility methods.
*/
public class Crypto {
/**
* Calculates the hash value of the given sub-array.
*
* @param algorithm the hash algorithm name
* @param buf the array holding the bytes to process
* @param off the offset within the array at which to start
* @param len the number of bytes to process
* @return the hash of the given sub-array
* @throws RuntimeException if no provider implements the give hash algorithm
*/
public static byte[] hash(String algorithm, byte[] buf, int off, int len) {
try {
MessageDigest digest = MessageDigest.getInstance(algorithm);
digest.update(buf, off, len);
return digest.digest();
} catch (NoSuchAlgorithmException nsae) {
throw new RuntimeException(nsae);
}
}
/**
* Calculates the hash value of the concatenated bytes of the given arrays.
*
* @param algorithm the hash algorithm name
* @param bufs the arrays holding the bytes to process
* @return the hash of the concatenated array bytes
* @throws RuntimeException if no provider implements the give hash algorithm
*/
public static byte[] hash(String algorithm, byte[]... bufs) {
try {
MessageDigest digest = MessageDigest.getInstance(algorithm);
for (byte[] buf : bufs)
digest.update(buf);
return digest.digest();
} catch (NoSuchAlgorithmException nsae) {
throw new RuntimeException(nsae);
}
}
/**
* Calculates the hash value of the bytes obtained from the given
* string using the given charset.
*
* @param algorithm the hash algorithm name
* @param s the string to process
* @param charset the charset used for obtaining the string bytes
* @return the hash of the string as an ASCII hex string
* @throws UnsupportedEncodingException if the given charset is not supported
* @throws RuntimeException if no provider implements the give hash algorithm
*/
public static String hash(String algorithm, String s, String charset) throws UnsupportedEncodingException {
byte[] buf = s.getBytes(charset);
return Strings.toHexRawString(hash(algorithm, buf)).toLowerCase();
}
/**
* Calculates the hash value of the bytes obtained from the given
* string using the ISO-8859-1 (ISO-Latin) charset.
*
* @param algorithm the hash algorithm name
* @param s the string to process
* @return the hash of the string as an ASCII hex string
* @throws RuntimeException if no provider implements the give hash algorithm
*/
public static String hash(String algorithm, String s) {
try {
return hash(algorithm, s, "ISO8859_1");
} catch (UnsupportedEncodingException uee) {
return null; // can never happen
}
}
/**
* Calculates the hash value of the bytes read from the given stream.
* The stream is closed by this method.
*
* @param algorithm the hash algorithm name
* @param in the stream holding the bytes to process
* @return the hash of the bytes read from the stream
* @throws RuntimeException if no provider implements the give hash algorithm
* @throws IOException if an error occurs while reading from the stream
*/
public static byte[] hash(String algorithm, InputStream in) throws IOException {
byte[] buf = new byte[8192];
int len;
try {
MessageDigest digest = MessageDigest.getInstance(algorithm);
BufferedInputStream bin = new BufferedInputStream(in);
while ((len = bin.read(buf)) != -1)
digest.update(buf, 0, len);
return digest.digest();
} catch (NoSuchAlgorithmException nsae) {
throw new RuntimeException(nsae);
} finally {
in.close();
}
}
/**
* Returns the block size of the given hash algorithm in bytes.
*
* @param algo the hash algorithm
* @return the block size in bytes
* @throws IllegalArgumentException if the blocks size for the given hash algorithm is unknown
*/
public static int getBlockSize(String algo) {
if ("MD5".equals(algo)
|| "SHA-0".equals(algo)
|| "SHA-1".equals(algo)
|| "SHA-224".equals(algo)
|| "SHA-256".equals(algo)) return 64;
if ("SHA-384".equals(algo)
|| "SHA-512".equals(algo)
|| "SHA-512/224".equals(algo)
|| "SHA-512/256".equals(algo)) return 128;
if ("SHA3-224".equals(algo)) return 144;
if ("SHA3-256".equals(algo)) return 136;
if ("SHA3-384".equals(algo)) return 104;
if ("SHA3-512".equals(algo)) return 72;
throw new IllegalArgumentException("block size for " + algo + " is unknown");
}
/**
* Calculates the RFC 2104 HMAC value using the specified hash function
* for the given data using the given key.
*
* @param algorithm the hash algorithm name
* @param key the key to hash with
* @param data the data to hash
* @return the HMAC value
* @throws IllegalArgumentException if the blocks size for the given hash algorithm is unknown
*/
public static byte[] hmac(String algorithm, byte[] key, byte[] data) {
return hmac(algorithm, getBlockSize(algorithm), key, data);
}
/**
* Calculates the RFC 2104 HMAC value using the specified hash function
* for the given data using the given key.
*
* Note: the hash function's block size is assumed to be 64 bytes (as is
* the case for MD5, SHA-1 and SHA-256).
*
* @param algorithm the hash algorithm name
* @param blockSize the hash algorithm's block size (in bytes)
* @param key the key to hash with
* @param data the data to hash
* @return the HMAC value
*/
public static byte[] hmac(String algorithm, int blockSize, byte[] key, byte[] data) {
// if too long, use the key's hash as the key
if (key.length > blockSize)
key = hash(algorithm, key);
// if too short, pad with zeros
if (key.length < blockSize)
key = Arrays.copyOf(key, blockSize);
// xor key with ipad and opad
byte[] kipad = new byte[blockSize];
byte[] kopad = new byte[blockSize];
for (int i = 0; i < blockSize; i++) {
kipad[i] = (byte)(key[i] ^ 0x36);
kopad[i] = (byte)(key[i] ^ 0x5C);
}
return hash(algorithm, kopad, hash(algorithm, kipad, data));
}
/**
* Calculates the RFC 4226 HOTP for a given secret and data.
*
* @param secret the shared secret (HMAC key)
* @param data the data on which th HMAC is computed, which can include
* the counter, time, or other value that changes on each use
* plus optional extra data (such as a PIN code)
* @param algo the hash algorithm to use
* @param digits the number of digits (of given radix) in the generated OTP
* @param radix the radix of the OTP digits (e.g. 10 for decimal, 16 for hex)
* @param truncationOffset the offset into the HMAC from which to begin truncation;
* if negative, then dynamic truncation will be used, i.e. the last 4 bits
* of the HMAC are used as the truncation offset
* @return an OTP string in the given radix containing the request number of digits
*/
public static String hotp(byte[] secret, byte[] data,
String algo, int digits, int radix, int truncationOffset) {
byte[] hash = hmac(algo, secret, data);
// get the truncated value from hmac
if (truncationOffset < 0)
truncationOffset = hash[hash.length - 1] & 0xf;
int value = (int)Utils.getU32(hash, truncationOffset, false) & 0x7fffffff;
// calculate modulo to limit number of digits
long max = 1;
for (int i = 0; i < digits && max != 0; i++) {
max *= radix;
if (max > Integer.MAX_VALUE)
max = 0;
}
if (max > 0)
value = value % (int)max;
// convert to string and pad with zeros
String otp = Integer.toString(value, radix);
return Strings.padZeros(otp, digits);
}
/**
* Calculates the RFC 4226 HOTP for a given secret, moving factor and optional extra data.
*
* @param secret the shared secret (HMAC key)
* @param movingFactor the counter, time, or other value that changes on each use
* @param extraData optional extra data (such as a PIN code) to combine with the moving factor
* @param algo the hash algorithm to use
* @param digits the number of digits (of given radix) in the generated OTP
* @param radix the radix of the OTP digits (e.g. 10 for decimal, 16 for hex)
* @param truncationOffset the offset into the HMAC from which to begin truncation;
* if negative, then dynamic truncation will be used, i.e. the last 4 bits
* of the HMAC are used as the truncation offset
* @return an OTP string in the given radix containing the request number of digits
*/
public static String hotp(byte[] secret, long movingFactor, byte[] extraData,
String algo, int digits, int radix, int truncationOffset) {
// get moving factor bytes and combine with optional extra data
int extra = extraData == null ? 0 : extraData.length;
byte[] data = new byte[extra + 8];
if (extra > 0)
System.arraycopy(extraData, 0, data, 0, extraData.length);
Utils.getBytes(movingFactor, false, data, extra, 8);
return hotp(secret, data, algo, digits, radix, truncationOffset);
}
/**
* Calculates the RFC 4226 HOTP for a given secret and moving factor.
*
* @param secret the shared secret (HMAC key)
* @param movingFactor the counter, time, or other value that changes on each use
* @param algo the hash algorithm to use
* @param digits the number of digits (of given radix) in the generated OTP
* @param radix the radix of the OTP digits (e.g. 10 for decimal, 16 for hex)
* @param truncationOffset the offset into the HMAC from which to begin truncation;
* if negative, then dynamic truncation will be used, i.e. the last 4 bits
* of the HMAC are used as the truncation offset
* @return an OTP string in the given radix containing the request number of digits
*/
public static String hotp(byte[] secret, long movingFactor,
String algo, int digits, int radix, int truncationOffset) {
return hotp(secret, movingFactor, null, algo, digits, radix, truncationOffset);
}
/**
* Calculates the RFC 4226 HOTP for a given secret and moving factor,
* using dynamic truncation and decimal (radix 10) digits.
*
* @param secret the shared secret (HMAC key)
* @param movingFactor the counter, time, or other value that changes on each use
* @param algo the hash algorithm to use
* @param digits the number of digits (of given radix) in the generated OTP
* @return an OTP string in the given radix containing the request number of digits
*/
public static String hotp(byte[] secret, long movingFactor, String algo, int digits) {
return hotp(secret, movingFactor, null, algo, digits, 10, -1);
}
/**
* Calculates the RFC 4226 HOTP for a given secret and moving factor,
* using the SHA-1 hash algorithm, dynamic truncation and decimal (radix 10) digits.
*
* @param secret the shared secret (HMAC key)
* @param movingFactor the counter, time, or other value that changes on each use
* @param digits the number of digits (of given radix) in the generated OTP
* @return an OTP string in the given radix containing the request number of digits
*/
public static String hotp(byte[] secret, long movingFactor, int digits) {
return hotp(secret, movingFactor, "SHA-1", digits);
}
/**
* Calculates the RFC 6238 TOTP for a given secret and timestamp.
*
* @param secret the shared secret (HMAC key)
* @param timestamp the timestamp to use (in millis since epoch)
* @param step the time step (validity interval per OTP) in seconds
* @param algo the hash algorithm to use
* @param digits the number of digits in the generated OTP
* @return an OTP string containing the request number of digits
*/
public static String totp(byte[] secret, long timestamp, int step, String algo, int digits) {
return hotp(secret, timestamp / 1000 / step, algo, digits, 10, -1);
}
/**
* Calculates the RFC 6238 TOTP for a given secret and timestamp,
* using the SHA-1 hash algorithm.
*
* @param secret the shared secret (HMAC key)
* @param timestamp the timestamp to use (in millis since epoch)
* @param step the time step (validity interval per OTP) in seconds
* @param digits the number of digits in the generated OTP
* @return an OTP string containing the request number of digits
*/
public static String totp(byte[] secret, long timestamp, int step, int digits) {
return totp(secret, timestamp, step, "SHA-1", digits);
}
/**
* Calculates the RFC 6238 TOTP for a given secret and the current timestamp,
* using the SHA-1 hash algorithm.
*
* @param secret the shared secret (HMAC key)
* @param step the time step (validity interval per OTP) in seconds
* @param digits the number of digits in the generated OTP
* @return an OTP string containing the request number of digits
*/
public static String totp(byte[] secret, int step, int digits) {
return totp(secret, System.currentTimeMillis(), step, "SHA-1", digits);
}
/**
* Validates that a given value matches the calculated RFC 6238 TOTP
* for a given secret and parameters, over a range of periods (steps).
*
* Since the client's clock may be a bit early or a bit late compared
* to the server's clock, and it may also take the user a few seconds
* between generating the totp value and finishing to type and submit it,
* his 'current' totp period at the moment he starts might be a bit off
* from the server's 'current' totp period at the moment it is validated.
*
* To account for this, we consider not only the current period's value
* to be correct, but those of a range of consecutive periods, from a bit
* before to a bit after the server's current period. In other words,
* we simulate an 'expansion' of the totp period so that it overlaps
* with a few previous and subsequent periods, all considered correct.
*
* Tweaking the range of periods is a tradeoff between security and usability.
*
* As with any use of OTP values, and especially when allowing for a range
* of valid values, it is important for the server to keep track of which
* OTP values have been used and never allow the same value to be used more
* than once, even if it is correct and still in the proper time period.
* It is the caller's responsibility to implement this safeguard.
*
* @param secret the shared secret (HMAC key)
* @param step the time step (period) in seconds
* @param algo the hash algorithm to use
* @param digits the number of digits in the generated OTP
* @param value the OTP value to validate
* @param rangeStart the start of the range of valid periods
* relative to the current period (inclusive),
* e.g. -2 for 2 periods before the current period (now)
* @param rangeEnd the end of the range of valid periods
* relative to the current period (inclusive),
* e.g. 3 for 3 periods before the current period (now)
* @return the relative index (from now) of the period for which
* the value is valid, or null if it is invalid in
* the entire range of periods
*/
public static Integer validateTotp(byte[] secret, int step, String algo, int digits,
String value, int rangeStart, int rangeEnd) {
if (rangeStart > rangeEnd)
throw new IllegalArgumentException("range start is greater than range end");
// iterate over range of acceptable periods, starting at 0 (current)
// and alternating negative and positive growing values
// (i.e. in decreasing order of clock skew likelihood)
long now = System.currentTimeMillis();
int periods = 1 + 2 * Math.max(Math.abs(rangeStart), Math.abs(rangeEnd));
for (int i = 0; i < periods; i++) {
int j = (i >>> 1) ^ -(i & 1); // convert unsigned to alternating (zigzag decoding)
if (j >= rangeStart && j <= rangeEnd
&& totp(secret, now + j * step * 1000L, step, algo, digits).equals(value))
return j;
}
return null;
}
/**
* Calculates the MD5 hash value of the given sub-array.
*
* @param buf the array holding the bytes to process
* @param off the offset within the array at which to start
* @param len the number of bytes to process
* @return the MD5 hash of the given sub-array
* @throws RuntimeException if no provider supports an MD5 implementation
*/
public static byte[] md5(byte[] buf, int off, int len) {
return hash("MD5", buf, off, len);
}
/**
* Calculates the MD5 hash value of the concatenated bytes of the given arrays.
*
* @param bufs the arrays holding the bytes to process
* @return the MD5 hash of the concatenated array bytes
* @throws RuntimeException if no provider supports an MD5 implementation
*/
public static byte[] md5(byte[]... bufs) {
return hash("MD5", bufs);
}
/**
* Calculates the MD5 hash value of the bytes obtained from the given
* string using the given charset.
*
* @param s the string to process
* @param charset the charset used for obtaining the string bytes
* @return the MD5 hash of the string as an ASCII hex string
* @throws UnsupportedEncodingException if the given charset is not supported
* @throws RuntimeException if no provider supports an MD5 implementation
*/
public static String md5(String s, String charset) throws UnsupportedEncodingException {
return hash("MD5", s, charset);
}
/**
* Calculates the MD5 hash value of the bytes obtained from the given
* string using the ISO-8859-1 (ISO-Latin) charset.
*
* @param s the string to process
* @return the MD5 hash of the string as an ASCII hex string
* @throws RuntimeException if no provider supports an MD5 implementation
*/
public static String md5(String s) {
return hash("MD5", s);
}
/**
* Calculates the MD5 hash value of the bytes read from the given stream.
* The stream is closed by this method.
*
* @param in the stream holding the bytes to process
* @return the MD5 hash of the bytes read from the stream
* @throws RuntimeException if no provider supports an MD5 implementation
* @throws IOException if an error occurs while reading from the stream
*/
public static byte[] md5(InputStream in) throws IOException {
return hash("MD5", in);
}
/**
* Calculates the RFC 2104 HMAC value using the MD5 hash function
* for the given data using the given key.
*
* @param key the key to hash with
* @param data the data to hash
* @return the HMAC value
*/
public static byte[] hmacMD5(byte[] key, byte[] data) {
return hmac("MD5", key, data);
}
/**
* Calculates the SHA-1 hash value of the given sub-array.
*
* @param buf the array holding the bytes to process
* @param off the offset within the array at which to start
* @param len the number of bytes to process
* @return the SHA-1 hash of the given sub-array
* @throws RuntimeException if no provider supports a SHA-1 implementation
*/
public static byte[] sha1(byte[] buf, int off, int len) {
return hash("SHA-1", buf, off, len);
}
/**
* Calculates the SHA-1 hash value of the concatenated bytes of the given arrays.
*
* @param bufs the arrays holding the bytes to process
* @return the SHA-1 hash of the concatenated array bytes
* @throws RuntimeException if no provider supports a SHA-1 implementation
*/
public static byte[] sha1(byte[]... bufs) {
return hash("SHA-1", bufs);
}
/**
* Calculates the SHA-1 hash value of the bytes obtained from the given
* string using the given charset.
*
* @param s the string to process
* @param charset the charset used for obtaining the string bytes
* @return the SHA-1 hash of the string as an ASCII hex string
* @throws UnsupportedEncodingException if the given charset is not supported
* @throws RuntimeException if no provider supports a SHA-1 implementation
*/
public static String sha1(String s, String charset) throws UnsupportedEncodingException {
return hash("SHA-1", s, charset);
}
/**
* Calculates the SHA-1 hash value of the bytes obtained from the given
* string using the ISO-8859-1 (ISO-Latin) charset.
*
* @param s the string to process
* @return the SHA-1 hash of the string as an ASCII hex string
* @throws RuntimeException if no provider supports a SHA-1 implementation
*/
public static String sha1(String s) {
return hash("SHA-1", s);
}
/**
* Calculates the SHA-1 hash value of the bytes read from the given stream.
* The stream is closed by this method.
*
* @param in the stream holding the bytes to process
* @return the SHA-1 hash of the bytes read from the stream
* @throws RuntimeException if no provider supports a SHA-1 implementation
* @throws IOException if an error occurs while reading from the stream
*/
public static byte[] sha1(InputStream in) throws IOException {
return hash("SHA-1", in);
}
/**
* Calculates the RFC 2104 HMAC value using the SHA-1 hash function
* for the given data using the given key.
*
* @param key the key to hash with
* @param data the data to hash
* @return the HMAC value
*/
public static byte[] hmacSHA1(byte[] key, byte[] data) {
return hmac("SHA-1", key, data);
}
/**
* Calculates the SHA-256 hash value of the given sub-array.
*
* @param buf the array holding the bytes to process
* @param off the offset within the array at which to start
* @param len the number of bytes to process
* @return the SHA-256 hash of the given sub-array
* @throws RuntimeException if no provider supports a SHA-256 implementation
*/
public static byte[] sha256(byte[] buf, int off, int len) {
return hash("SHA-256", buf, off, len);
}
/**
* Calculates the SHA-256 hash value of the concatenated bytes of the given arrays.
*
* @param bufs the arrays holding the bytes to process
* @return the SHA-256 hash of the concatenated array bytes
* @throws RuntimeException if no provider supports a SHA-256 implementation
*/
public static byte[] sha256(byte[]... bufs) {
return hash("SHA-256", bufs);
}
/**
* Calculates the SHA-256 hash value of the bytes obtained from the given
* string using the given charset.
*
* @param s the string to process
* @param charset the charset used for obtaining the string bytes
* @return the SHA-256 hash of the string as an ASCII hex string
* @throws UnsupportedEncodingException if the given charset is not supported
* @throws RuntimeException if no provider supports a SHA-256 implementation
*/
public static String sha256(String s, String charset) throws UnsupportedEncodingException {
return hash("SHA-256", s, charset);
}
/**
* Calculates the SHA-256 hash value of the bytes obtained from the given
* string using the ISO-8859-1 (ISO-Latin) charset.
*
* @param s the string to process
* @return the SHA-256 hash of the string as an ASCII hex string
* @throws RuntimeException if no provider supports a SHA-256 implementation
*/
public static String sha256(String s) {
return hash("SHA-256", s);
}
/**
* Calculates the SHA-256 hash value of the bytes read from the given stream.
* The stream is closed by this method.
*
* @param in the stream holding the bytes to process
* @return the SHA-256 hash of the bytes read from the stream
* @throws RuntimeException if no provider supports a SHA-256 implementation
* @throws IOException if an error occurs while reading from the stream
*/
public static byte[] sha256(InputStream in) throws IOException {
return hash("SHA-256", in);
}
/**
* Calculates the RFC 2104 HMAC value using the SHA-256 hash function
* for the given data using the given key.
*
* @param key the key to hash with
* @param data the data to hash
* @return the HMAC value
*/
public static byte[] hmacSHA256(byte[] key, byte[] data) {
return hmac("SHA-256", key, data);
}
/**
* Creates and initializes an SSLContext, which can be used to get socket factories etc.
*
* @param keystore the full path to a keystore file
* @param ksType the key store type (e.g. "JKS" or "PKCS12")
* @param ksProvider the key store provider (e.g. "SUN"), null for default
* @param ksPass the key store's password
* @param keyPass the key's password
* @param algorithm the key algorithm (e.g. "SunX509")
* @param algoProvider the algorithm provider (e.g. "SunJSSE"), null for default
* @param protocol the specific SSL protocol (e.g. "SSLv3" or "TLS")
* @param protoProvider the protocol provider (e.g. "SunJSSE"), null for default
* @return the initialized SSLContext
* @throws IOException if an error occurs in any of the initialization steps;
* the nested exception can be used to determine the exact cause
*/
public static SSLContext createSSLContext(String keystore, String ksType, String ksProvider,
String ksPass, String keyPass, String algorithm, String algoProvider,
String protocol, String protoProvider) throws IOException {
// keystore can be created using:
// ${JAVA_HOME}/bin/keytool -keystore /path/to/keystore.jks -alias myalias -genkey -keyalg RSA
try (FileInputStream in = new FileInputStream(keystore)) {
KeyStore ks = ksProvider == null
? KeyStore.getInstance(ksType) : KeyStore.getInstance(ksType, ksProvider);
ks.load(in, ksPass.toCharArray());
KeyManagerFactory kmf = algoProvider == null
? KeyManagerFactory.getInstance(algorithm) : KeyManagerFactory.getInstance(algorithm, algoProvider);
kmf.init(ks, keyPass.toCharArray());
SSLContext sslcontext = protoProvider == null
? SSLContext.getInstance(protocol) : SSLContext.getInstance(protocol, protoProvider);
sslcontext.init(kmf.getKeyManagers(), null, null);
return sslcontext;
} catch (Exception e) {
throw new IOException("error initializing SSLContext", e);
}
}
/**
* Converts a raw hex string representing an ECC private key to a corresponding {#link PrivateKey} instance.
*
* @param rawHexPrivateKey a raw hex string representing an ECC private key
* @return the private key
* @throws InvalidParameterSpecException if the appropriate EC parameter spec (secp256r1) is unsupported
* @throws NoSuchAlgorithmException if JVM does not support the EC algorithm
* @throws InvalidKeySpecException if the key data is inappropriate for the encryption scheme
*/
public static PrivateKey toECPrivateKey(String rawHexPrivateKey) throws InvalidParameterSpecException,
NoSuchAlgorithmException, InvalidKeySpecException {
BigInteger keyValue = new BigInteger(rawHexPrivateKey, 16);
AlgorithmParameters parameters = AlgorithmParameters.getInstance("EC");
parameters.init(new ECGenParameterSpec("secp256r1"));
ECParameterSpec ecSpec = parameters.getParameterSpec(ECParameterSpec.class);
ECPrivateKeySpec keySpec = new ECPrivateKeySpec(keyValue, ecSpec);
KeyFactory kf = KeyFactory.getInstance("EC");
return kf.generatePrivate(keySpec);
}
/**
* Calculates the ECC signature of the given data using the given private key.
*
* @param rawHexPrivateKey a raw hex string representing an ECC private key
* @param data the data to sign
* @return the DER-formatted signature (as returned from {#link Signature.sign()})
* @throws NoSuchAlgorithmException if JVM does not support the EC algorithm
* @throws InvalidKeySpecException if the key data is inappropriate for the encryption scheme
* @throws InvalidParameterSpecException if the appropriate EC parameter spec (secp256r1) is unsupported
* @throws InvalidKeyException if the key is invalid
* @throws SignatureException if the input data cannot be processed properly
*/
public static byte[] getECSignature(String rawHexPrivateKey, byte[] data)
throws NoSuchAlgorithmException, InvalidKeySpecException, InvalidParameterSpecException,
InvalidKeyException, SignatureException {
PrivateKey privkey = toECPrivateKey(rawHexPrivateKey);
Signature ecdsaSign = Signature.getInstance("SHA256withECDSA");
ecdsaSign.initSign(privkey);
ecdsaSign.update(data);
return ecdsaSign.sign();
}
/**
* Converts an array of DER-formatted ECDSA signature bytes (as returned from {#link Signature.sign()})
* into the concatenated R and S raw signature bytes.
*
* @param derSignature the DER-formatted ECDSA signature bytes
* @return the concatenated R and S raw signature bytes
* @throws IOException if the given signature bytes are invalid
*/
public static byte[] toECRawSignature(byte[] derSignature) throws IOException {
// wrapping sequence
DERParser p = new DERParser(derSignature);
p.next(0x30); // sequence
p = p.parseContent();
// r value
p.next(2); // integer
byte[] r = p.getContent();
int rpad = r[0] == 0 ? 1 : 0;
// s value
p.next(2); // integer
byte[] s = p.getContent();
int spad = s[0] == 0 ? 1 : 0;
// concatenate r and s values in result (removing DER's leading 0 byte prefix for negative integers)
byte[] rs = new byte[r.length + s.length - rpad - spad];
System.arraycopy(r, rpad, rs, 0, r.length - rpad);
System.arraycopy(s, spad, rs, r.length - rpad, s.length - spad);
return rs;
}
public static void main(String... args) {
try {
Arguments arguments = new Arguments()
.add("algorithm", "a", "SHA-1")
.add("step", "s", int.class, 30)
.add("counter", "c", long.class, 0L)
.add("digits", "d", int.class, 6)
.add("key", "k", String.class, true)
.parse(args);
if (arguments.count() == 0)
throw new IllegalArgumentException();
String command = arguments.get(0);
String algorithm = arguments.get("algorithm");
if (command.equals("hash")) {
System.out.println(Crypto.hash(algorithm, arguments.get(1)));
} else if (command.equals("hotp")) {
long counter = arguments.get("counter");
int digits = arguments.get("digits");
String encodedKey = arguments.get("key");
byte[] key = Base32.decode(encodedKey.toUpperCase(), true);
System.out.println(Crypto.hotp(key, counter, algorithm, digits));
} else if (command.equals("totp")) {
int step = arguments.get("step");
int digits = arguments.get("digits");
String encodedKey = arguments.get("key");
byte[] key = Base32.decode(encodedKey.toUpperCase(), true);
System.out.println(Crypto.totp(key, System.currentTimeMillis(), step, algorithm, digits));
} else {
throw new IllegalArgumentException("invalid arguments");
}
} catch (IllegalArgumentException iae) {
if (iae.getMessage() != null)
System.err.println("Error: " + iae.getMessage());
System.err.println("\nUsage: java " + Crypto.class.getName() + " [options...]");
System.err.println("\nCommands:");
System.err.println(" hash [-a algorithm] \tcalculate hash of text");
System.err.println(" algorithm\tthe hash algorithm [default: SHA-1]");
System.err.println(" text \tthe text to hash");
System.err.println(" totp -k [-a algorithm] [-s step] [-d digits]\tgenerate a TOTP code");
System.err.println(" key \tthe base-32 encoded secret key");
System.err.println(" algorithm\tthe hash algorithm [default: SHA-1]");
System.err.println(" step \tthe time step in seconds [default: 30]");
System.err.println(" digits \tthe generated code length [default: 6]");
System.err.println(" hotp -k [-a algorithm] [-c counter] [-d digits]\tgenerate an HOTP code");
System.err.println(" key \tthe base-32 encoded secret key");
System.err.println(" algorithm\tthe hash algorithm [default: SHA-1]");
System.err.println(" counter \tthe counter value [default: 0]");
System.err.println(" digits \tthe generated code length [default: 6]");
}
}
}