Crypto-package refactoring


(Konstantinos Chalkias) #1

I am trying to organise the crypto-package, so that
a) any crypto-related functionality moves to the crypto-package (eg MerkleTrees used to be in MerkleTransaction)
b) add support for multiple signature algorithms
c) utilities to simplify the act of signing, random generation, encoding etc.
d) run some unit-tests

Here are some examples:

/**
 * Generate a securely random [ByteArray] of requested number of bytes. Usually used for seeds, nonces and keys.
 * In this version, the NativePRNGNonBlocking is exclusively used on Linux OS to utilize dev/urandom because in high traffic
 * /dev/random may wait for a certain amount of "noise" to be generated on the host machine before returning a result.
 *
 * On Solaris, Linux, and OS X, if the entropy gathering device in java.security is set to file:/dev/urandom
 * or file:/dev/random, then NativePRNG is preferred to SHA1PRNG. Otherwise, SHA1PRNG is preferred.
 * @see <a href="https://docs.oracle.com/javase/8/docs/technotes/guides/security/SunProviders.html#SecureRandomImp">SecureRandom Implementation</a>.
 *
 * If both dev/random and dev/urandom are available, then dev/random is only preferred over dev/urandom during VM boot
 * where it may be possible that OS didn't yet collect enough entropy to fill the randomness pool for the 1st time.
 * TODO: check default settings per OS and random/urandom availability
 *
 * @param numOfBytes how many random bytes to output.
 * @return a random [ByteArray].
 */
fun saferand(numOfBytes: Int): ByteArray {
    val bytes = ByteArray(numOfBytes)
    if (System.getProperty("os.name") == "Linux") {
        SecureRandom.getInstance("NativePRNGNonBlocking").nextBytes(bytes)
    } else {
        SecureRandom.getInstanceStrong().nextBytes(bytes)
    }
    return ByteArray(numOfBytes)
}

/**
 * Generic way to sign [ByteArray] messages with a [KeyPair]. Strategy on the actual signing algorithm is based
 * on the [PrivateKey] type. Note that I am not using the common OO Strategy Pattern.
 *
 * @param bytesToSign the message to be signed in [ByteArray] form.
 * @param keyPair the [KeyPair] (priv, pub) of the signer.
 * @return the digital signature on the message with extra info that identifies who the public key is owned by.
 */
fun sign(bytesToSign: ByteArray, keyPair: KeyPair): DigitalSignature.WithKey {
    val key = keyPair.private
    when (key) {
        is EdDSAPrivateKey -> return keyPair.signWithECDSA(bytesToSign) // TODO: Proper way for checking type.
        else -> throw CryptoException("Signing not supported for key-pair: ${keyPair}") // TODO: Decide on the log-format.
    }
}

/**
 * Generic Exception for crypto-related issues
 * @param reason a [String] message to be appended to the error log
 * @return an Exception with an error-log message
*/
class CryptoException(val reason: String) : Exception() {
    override fun toString() = "Crypto exception. Reason: $reason"
}

and for EncodingUtils

/**
 * [ByteArray]
 */

// Base58.
fun ByteArray.toBase58() =
        Base58.encode(this)

// Base64.
fun ByteArray.toBase64() =
        Base64.getEncoder().encodeToString(this)

// Hex (or Base16).
fun ByteArray.toHex() : String {
    val result = StringBuffer()
    forEach {
        val octet = it.toInt()
        val firstIndex = (octet and 0xF0).ushr(4)
        val secondIndex = octet and 0x0F
        result.append(HEX_CHARS[firstIndex])
        result.append(HEX_CHARS[secondIndex])
    }
    return result.toString()
}

/**
 * [String]
 */

// Hex to ByteArray
fun String.hexToByteArray() : ByteArray {
    val result = ByteArray(length / 2)
    for (i in 0 until length step 2) {
        val firstIndex = HEX_CHARS.indexOf(this[i]);
        val secondIndex = HEX_CHARS.indexOf(this[i + 1]);

        val octet = firstIndex.shl(4).or(secondIndex)
        result.set(i.shr(1), octet.toByte())
    }
    return result
}