All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.freeutils.util.Crypto Maven / Gradle / Ivy

The newest version!
/*
 *  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]"); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy