diff --git a/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip49PrivKeyEnc/PBKDF.java b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip49PrivKeyEnc/PBKDF.java deleted file mode 100644 index 5c2300431..000000000 --- a/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip49PrivKeyEnc/PBKDF.java +++ /dev/null @@ -1,89 +0,0 @@ -// Copyright (C) 2011 - Will Glozer. All rights reserved. - -package com.vitorpamplona.quartz.nip49PrivKeyEnc; - -import static java.lang.System.arraycopy; - -import java.security.GeneralSecurityException; - -import javax.crypto.Mac; -import javax.crypto.spec.SecretKeySpec; - -/** - * An implementation of the Password-Based Key Derivation Function as specified - * in RFC 2898. - * - * @author Will Glozer - */ -public class PBKDF { - /** - * Implementation of PBKDF2 (RFC2898). - * - * @param alg HMAC algorithm to use. - * @param P Password. - * @param S Salt. - * @param c Iteration count. - * @param dkLen Intended length, in octets, of the derived key. - * - * @return The derived key. - * - * @throws GeneralSecurityException - */ - public static byte[] pbkdf2(String alg, byte[] P, byte[] S, int c, int dkLen) throws GeneralSecurityException { - Mac mac = Mac.getInstance(alg); - mac.init(new SecretKeySpec(P, alg)); - byte[] DK = new byte[dkLen]; - pbkdf2(mac, S, c, DK, dkLen); - return DK; - } - - /** - * Implementation of PBKDF2 (RFC2898). - * - * @param mac Pre-initialized {@link Mac} instance to use. - * @param S Salt. - * @param c Iteration count. - * @param DK Byte array that derived key will be placed in. - * @param dkLen Intended length, in octets, of the derived key. - * - * @throws GeneralSecurityException - */ - public static void pbkdf2(Mac mac, byte[] S, int c, byte[] DK, int dkLen) throws GeneralSecurityException { - int hLen = mac.getMacLength(); - - if (dkLen > (Math.pow(2, 32) - 1) * hLen) { - throw new GeneralSecurityException("Requested key length too long"); - } - - byte[] U = new byte[hLen]; - byte[] T = new byte[hLen]; - byte[] block1 = new byte[S.length + 4]; - - int l = (int) Math.ceil((double) dkLen / hLen); - int r = dkLen - (l - 1) * hLen; - - arraycopy(S, 0, block1, 0, S.length); - - for (int i = 1; i <= l; i++) { - block1[S.length + 0] = (byte) (i >> 24 & 0xff); - block1[S.length + 1] = (byte) (i >> 16 & 0xff); - block1[S.length + 2] = (byte) (i >> 8 & 0xff); - block1[S.length + 3] = (byte) (i >> 0 & 0xff); - - mac.update(block1); - mac.doFinal(U, 0); - arraycopy(U, 0, T, 0, hLen); - - for (int j = 1; j < c; j++) { - mac.update(U); - mac.doFinal(U, 0); - - for (int k = 0; k < hLen; k++) { - T[k] ^= U[k]; - } - } - - arraycopy(T, 0, DK, (i - 1) * hLen, (i == l ? r : hLen)); - } - } -} \ No newline at end of file diff --git a/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip49PrivKeyEnc/PBKDF.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip49PrivKeyEnc/PBKDF.kt new file mode 100644 index 000000000..bcfbcfc6e --- /dev/null +++ b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip49PrivKeyEnc/PBKDF.kt @@ -0,0 +1,121 @@ +/** + * Copyright (c) 2025 Vitor Pamplona + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the + * Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package com.vitorpamplona.quartz.nip49PrivKeyEnc + +import java.security.GeneralSecurityException +import javax.crypto.Mac +import javax.crypto.spec.SecretKeySpec +import kotlin.math.ceil +import kotlin.math.pow + +/** + * An implementation of the Password-Based Key Derivation Function as specified + * in RFC 2898. + * + * @author Will Glozer + */ +object PBKDF { + /** + * Implementation of PBKDF2 (RFC2898). + * + * @param alg HMAC algorithm to use. + * @param password Password. + * @param salt Salt. + * @param iterationCount Iteration count. + * @param dkLen Intended length, in octets, of the derived key. + * + * @return The derived key. + * + * @throws GeneralSecurityException + */ + @Throws(GeneralSecurityException::class) + fun pbkdf2( + alg: String, + password: ByteArray, + salt: ByteArray, + iterationCount: Int, + dkLen: Int, + ): ByteArray { + val mac = Mac.getInstance(alg) + mac.init(SecretKeySpec(password, alg)) + val destKey = ByteArray(dkLen) + pbkdf2(mac, salt, iterationCount, destKey, dkLen) + return destKey + } + + /** + * Implementation of PBKDF2 (RFC2898). + * + * @param mac Pre-initialized [Mac] instance to use. + * @param salt Salt. + * @param c Iteration count. + * @param destKey Byte array that derived key will be placed in. + * @param dkLen Intended length, in octets, of the derived key. + * + * @throws GeneralSecurityException + */ + @JvmStatic + @Throws(GeneralSecurityException::class) + fun pbkdf2( + mac: Mac, + salt: ByteArray, + c: Int, + destKey: ByteArray, + dkLen: Int, + ) { + val hLen = mac.getMacLength() + + if (dkLen > (2.0.pow(32.0) - 1) * hLen) { + throw GeneralSecurityException("Requested key length too long") + } + + val uArray = ByteArray(hLen) + val rArray = ByteArray(hLen) + val block1 = ByteArray(salt.size + 4) + + val l = ceil(dkLen.toDouble() / hLen).toInt() + val r = dkLen - (l - 1) * hLen + + System.arraycopy(salt, 0, block1, 0, salt.size) + + for (i in 1..l) { + block1[salt.size + 0] = (i shr 24 and 0xff).toByte() + block1[salt.size + 1] = (i shr 16 and 0xff).toByte() + block1[salt.size + 2] = (i shr 8 and 0xff).toByte() + block1[salt.size + 3] = (i shr 0 and 0xff).toByte() + + mac.update(block1) + mac.doFinal(uArray, 0) + System.arraycopy(uArray, 0, rArray, 0, hLen) + + for (j in 1..scrypt - * key derivation function. This class will attempt to load a native library - * containing the optimized C implementation from - * http://www.tarsnap.com/scrypt.html and - * fall back to the pure Java version if that fails. - * - * @author Will Glozer - */ -public class SCrypt { - - /** - * Implementation of the scrypt KDF. - * - * @param passwd Password. - * @param salt Salt. - * @param N CPU cost parameter. - * @param r Memory cost parameter. - * @param p Parallelization parameter. - * @param dkLen Intended length of the derived key. - * - * @return The derived key. - * - * @throws GeneralSecurityException when HMAC_SHA256 is not available. - */ - public static byte[] scrypt(byte[] passwd, byte[] salt, int N, int r, int p, int dkLen) throws GeneralSecurityException { - return scryptJ(passwd, salt, N, r, p, dkLen); - } - - /** - * Pure Java implementation of the scrypt KDF. - * - * @param passwd Password. - * @param salt Salt. - * @param N CPU cost parameter. - * @param r Memory cost parameter. - * @param p Parallelization parameter. - * @param dkLen Intended length of the derived key. - * - * @return The derived key. - * - * @throws GeneralSecurityException when HMAC_SHA256 is not available. - */ - public static byte[] scryptJ(byte[] passwd, byte[] salt, int N, int r, int p, int dkLen) throws GeneralSecurityException { - if (N < 2 || (N & (N - 1)) != 0) throw new IllegalArgumentException("N must be a power of 2 greater than 1"); - - if (N > MAX_VALUE / 128 / r) throw new IllegalArgumentException("Parameter N is too large"); - if (r > MAX_VALUE / 128 / p) throw new IllegalArgumentException("Parameter r is too large"); - - Mac mac = Mac.getInstance("HmacSHA256"); - mac.init(new SecretKeyOrEmptySpec(passwd, "HmacSHA256")); - - byte[] DK = new byte[dkLen]; - - byte[] B = new byte[128 * r * p]; - byte[] XY = new byte[256 * r]; - byte[] V = new byte[128 * r * N]; - int i; - - PBKDF.pbkdf2(mac, salt, 1, B, p * 128 * r); - - for (i = 0; i < p; i++) { - smix(B, i * 128 * r, r, N, V, XY); - } - - PBKDF.pbkdf2(mac, B, 1, DK, dkLen); - - return DK; - } - - public static void smix(byte[] B, int Bi, int r, int N, byte[] V, byte[] XY) { - int Xi = 0; - int Yi = 128 * r; - int i; - - arraycopy(B, Bi, XY, Xi, 128 * r); - - for (i = 0; i < N; i++) { - arraycopy(XY, Xi, V, i * (128 * r), 128 * r); - blockmix_salsa8(XY, Xi, Yi, r); - } - - for (i = 0; i < N; i++) { - int j = integerify(XY, Xi, r) & (N - 1); - blockxor(V, j * (128 * r), XY, Xi, 128 * r); - blockmix_salsa8(XY, Xi, Yi, r); - } - - arraycopy(XY, Xi, B, Bi, 128 * r); - } - - public static void blockmix_salsa8(byte[] BY, int Bi, int Yi, int r) { - byte[] X = new byte[64]; - int i; - - arraycopy(BY, Bi + (2 * r - 1) * 64, X, 0, 64); - - for (i = 0; i < 2 * r; i++) { - blockxor(BY, i * 64, X, 0, 64); - salsa20_8(X); - arraycopy(X, 0, BY, Yi + (i * 64), 64); - } - - for (i = 0; i < r; i++) { - arraycopy(BY, Yi + (i * 2) * 64, BY, Bi + (i * 64), 64); - } - - for (i = 0; i < r; i++) { - arraycopy(BY, Yi + (i * 2 + 1) * 64, BY, Bi + (i + r) * 64, 64); - } - } - - public static int R(int a, int b) { - return (a << b) | (a >>> (32 - b)); - } - - public static void salsa20_8(byte[] B) { - int[] B32 = new int[16]; - int[] x = new int[16]; - int i; - - for (i = 0; i < 16; i++) { - B32[i] = (B[i * 4 + 0] & 0xff) << 0; - B32[i] |= (B[i * 4 + 1] & 0xff) << 8; - B32[i] |= (B[i * 4 + 2] & 0xff) << 16; - B32[i] |= (B[i * 4 + 3] & 0xff) << 24; - } - - arraycopy(B32, 0, x, 0, 16); - - for (i = 8; i > 0; i -= 2) { - x[ 4] ^= R(x[ 0]+x[12], 7); x[ 8] ^= R(x[ 4]+x[ 0], 9); - x[12] ^= R(x[ 8]+x[ 4],13); x[ 0] ^= R(x[12]+x[ 8],18); - x[ 9] ^= R(x[ 5]+x[ 1], 7); x[13] ^= R(x[ 9]+x[ 5], 9); - x[ 1] ^= R(x[13]+x[ 9],13); x[ 5] ^= R(x[ 1]+x[13],18); - x[14] ^= R(x[10]+x[ 6], 7); x[ 2] ^= R(x[14]+x[10], 9); - x[ 6] ^= R(x[ 2]+x[14],13); x[10] ^= R(x[ 6]+x[ 2],18); - x[ 3] ^= R(x[15]+x[11], 7); x[ 7] ^= R(x[ 3]+x[15], 9); - x[11] ^= R(x[ 7]+x[ 3],13); x[15] ^= R(x[11]+x[ 7],18); - x[ 1] ^= R(x[ 0]+x[ 3], 7); x[ 2] ^= R(x[ 1]+x[ 0], 9); - x[ 3] ^= R(x[ 2]+x[ 1],13); x[ 0] ^= R(x[ 3]+x[ 2],18); - x[ 6] ^= R(x[ 5]+x[ 4], 7); x[ 7] ^= R(x[ 6]+x[ 5], 9); - x[ 4] ^= R(x[ 7]+x[ 6],13); x[ 5] ^= R(x[ 4]+x[ 7],18); - x[11] ^= R(x[10]+x[ 9], 7); x[ 8] ^= R(x[11]+x[10], 9); - x[ 9] ^= R(x[ 8]+x[11],13); x[10] ^= R(x[ 9]+x[ 8],18); - x[12] ^= R(x[15]+x[14], 7); x[13] ^= R(x[12]+x[15], 9); - x[14] ^= R(x[13]+x[12],13); x[15] ^= R(x[14]+x[13],18); - } - - for (i = 0; i < 16; ++i) B32[i] = x[i] + B32[i]; - - for (i = 0; i < 16; i++) { - B[i * 4 + 0] = (byte) (B32[i] >> 0 & 0xff); - B[i * 4 + 1] = (byte) (B32[i] >> 8 & 0xff); - B[i * 4 + 2] = (byte) (B32[i] >> 16 & 0xff); - B[i * 4 + 3] = (byte) (B32[i] >> 24 & 0xff); - } - } - - public static void blockxor(byte[] S, int Si, byte[] D, int Di, int len) { - for (int i = 0; i < len; i++) { - D[Di + i] ^= S[Si + i]; - } - } - - public static int integerify(byte[] B, int Bi, int r) { - int n; - - Bi += (2 * r - 1) * 64; - - n = (B[Bi + 0] & 0xff) << 0; - n |= (B[Bi + 1] & 0xff) << 8; - n |= (B[Bi + 2] & 0xff) << 16; - n |= (B[Bi + 3] & 0xff) << 24; - - return n; - } -} \ No newline at end of file diff --git a/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip49PrivKeyEnc/SCrypt.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip49PrivKeyEnc/SCrypt.kt new file mode 100644 index 000000000..096d4e6c3 --- /dev/null +++ b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip49PrivKeyEnc/SCrypt.kt @@ -0,0 +1,280 @@ +/** + * Copyright (c) 2025 Vitor Pamplona + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the + * Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package com.vitorpamplona.quartz.nip49PrivKeyEnc + +import com.vitorpamplona.quartz.nip49PrivKeyEnc.PBKDF.pbkdf2 +import java.security.GeneralSecurityException +import javax.crypto.Mac + +/** + * An implementation of the [](http://www.tarsnap.com/scrypt/scrypt.pdf)scrypt + * key derivation function. This class will attempt to load a native library + * containing the optimized C implementation from + * [http://www.tarsnap.com/scrypt.html and + * fall back to the pure Java version if that fails. + * + * @author Will Glozer +](http://www.tarsnap.com/scrypt.html) */ +object SCrypt { + /** + * Implementation of the [](http://www.tarsnap.com/scrypt/scrypt.pdf)scrypt KDF. + * + * @param passwd Password. + * @param salt Salt. + * @param n CPU cost parameter. + * @param r Memory cost parameter. + * @param p Parallelization parameter. + * @param dkLen Intended length of the derived key. + * + * @return The derived key. + * + * @throws GeneralSecurityException when HMAC_SHA256 is not available. + */ + @Throws(GeneralSecurityException::class) + fun scrypt( + passwd: ByteArray, + salt: ByteArray, + n: Int, + r: Int, + p: Int, + dkLen: Int, + ): ByteArray = scryptJ(passwd, salt, n, r, p, dkLen) + + /** + * Pure Java implementation of the [](http://www.tarsnap.com/scrypt/scrypt.pdf)scrypt KDF. + * + * @param passwd Password. + * @param salt Salt. + * @param n CPU cost parameter. + * @param r Memory cost parameter. + * @param p Parallelization parameter. + * @param dkLen Intended length of the derived key. + * + * @return The derived key. + * + * @throws GeneralSecurityException when HMAC_SHA256 is not available. + */ + @Throws(GeneralSecurityException::class) + fun scryptJ( + passwd: ByteArray, + salt: ByteArray, + n: Int, + r: Int, + p: Int, + dkLen: Int, + ): ByteArray { + require(!(n < 2 || (n and (n - 1)) != 0)) { "N must be a power of 2 greater than 1" } + + require(n <= Int.Companion.MAX_VALUE / 128 / r) { "Parameter N is too large" } + require(r <= Int.Companion.MAX_VALUE / 128 / p) { "Parameter r is too large" } + + val mac = Mac.getInstance("HmacSHA256") + mac.init(SecretKeyOrEmptySpec(passwd, "HmacSHA256")) + + val deskKey = ByteArray(dkLen) + + val bArray = ByteArray(128 * r * p) + val xyArray = ByteArray(256 * r) + val vArray = ByteArray(128 * r * n) + var i: Int + + pbkdf2(mac, salt, 1, bArray, p * 128 * r) + + i = 0 + while (i < p) { + smix(bArray, i * 128 * r, r, n, vArray, xyArray) + i++ + } + + pbkdf2(mac, bArray, 1, deskKey, dkLen) + + return deskKey + } + + fun smix( + bArray: ByteArray, + biArray: Int, + r: Int, + n: Int, + vArray: ByteArray, + xyArray: ByteArray, + ) { + val xIndex = 0 + val yIndex = 128 * r + var i: Int + + System.arraycopy(bArray, biArray, xyArray, xIndex, 128 * r) + + i = 0 + while (i < n) { + System.arraycopy(xyArray, xIndex, vArray, i * (128 * r), 128 * r) + blockmixSalsa8(xyArray, xIndex, yIndex, r) + i++ + } + + i = 0 + while (i < n) { + val j = integerify(xyArray, xIndex, r) and (n - 1) + blockxor(vArray, j * (128 * r), xyArray, xIndex, 128 * r) + blockmixSalsa8(xyArray, xIndex, yIndex, r) + i++ + } + + System.arraycopy(xyArray, xIndex, bArray, biArray, 128 * r) + } + + fun blockmixSalsa8( + byArray: ByteArray, + biArray: Int, + yi: Int, + r: Int, + ) { + val xArray = ByteArray(64) + var i: Int + + System.arraycopy(byArray, biArray + (2 * r - 1) * 64, xArray, 0, 64) + + i = 0 + while (i < 2 * r) { + blockxor(byArray, i * 64, xArray, 0, 64) + salsa20x8(xArray) + System.arraycopy(xArray, 0, byArray, yi + (i * 64), 64) + i++ + } + + i = 0 + while (i < r) { + System.arraycopy(byArray, yi + (i * 2) * 64, byArray, biArray + (i * 64), 64) + i++ + } + + i = 0 + while (i < r) { + System.arraycopy(byArray, yi + (i * 2 + 1) * 64, byArray, biArray + (i + r) * 64, 64) + i++ + } + } + + // NOT SURE IF THIS IS ACTUALLY A ROTATE. Ignore the name + fun rotate( + a: Int, + b: Int, + ): Int = (a shl b) or (a ushr (32 - b)) + + fun salsa20x8(bArray: ByteArray) { + val b32 = IntArray(16) + val x = IntArray(16) + var i: Int + + i = 0 + while (i < 16) { + b32[i] = (bArray[i * 4 + 0].toInt() and 0xff) shl 0 + b32[i] = b32[i] or ((bArray[i * 4 + 1].toInt() and 0xff) shl 8) + b32[i] = b32[i] or ((bArray[i * 4 + 2].toInt() and 0xff) shl 16) + b32[i] = b32[i] or ((bArray[i * 4 + 3].toInt() and 0xff) shl 24) + i++ + } + + System.arraycopy(b32, 0, x, 0, 16) + + i = 8 + while (i > 0) { + x[4] = x[4] xor rotate(x[0] + x[12], 7) + x[8] = x[8] xor rotate(x[4] + x[0], 9) + x[12] = x[12] xor rotate(x[8] + x[4], 13) + x[0] = x[0] xor rotate(x[12] + x[8], 18) + x[9] = x[9] xor rotate(x[5] + x[1], 7) + x[13] = x[13] xor rotate(x[9] + x[5], 9) + x[1] = x[1] xor rotate(x[13] + x[9], 13) + x[5] = x[5] xor rotate(x[1] + x[13], 18) + x[14] = x[14] xor rotate(x[10] + x[6], 7) + x[2] = x[2] xor rotate(x[14] + x[10], 9) + x[6] = x[6] xor rotate(x[2] + x[14], 13) + x[10] = x[10] xor rotate(x[6] + x[2], 18) + x[3] = x[3] xor rotate(x[15] + x[11], 7) + x[7] = x[7] xor rotate(x[3] + x[15], 9) + x[11] = x[11] xor rotate(x[7] + x[3], 13) + x[15] = x[15] xor rotate(x[11] + x[7], 18) + x[1] = x[1] xor rotate(x[0] + x[3], 7) + x[2] = x[2] xor rotate(x[1] + x[0], 9) + x[3] = x[3] xor rotate(x[2] + x[1], 13) + x[0] = x[0] xor rotate(x[3] + x[2], 18) + x[6] = x[6] xor rotate(x[5] + x[4], 7) + x[7] = x[7] xor rotate(x[6] + x[5], 9) + x[4] = x[4] xor rotate(x[7] + x[6], 13) + x[5] = x[5] xor rotate(x[4] + x[7], 18) + x[11] = x[11] xor rotate(x[10] + x[9], 7) + x[8] = x[8] xor rotate(x[11] + x[10], 9) + x[9] = x[9] xor rotate(x[8] + x[11], 13) + x[10] = x[10] xor rotate(x[9] + x[8], 18) + x[12] = x[12] xor rotate(x[15] + x[14], 7) + x[13] = x[13] xor rotate(x[12] + x[15], 9) + x[14] = x[14] xor rotate(x[13] + x[12], 13) + x[15] = x[15] xor rotate(x[14] + x[13], 18) + i -= 2 + } + + i = 0 + while (i < 16) { + b32[i] = x[i] + b32[i] + ++i + } + + i = 0 + while (i < 16) { + bArray[i * 4 + 0] = (b32[i] shr 0 and 0xff).toByte() + bArray[i * 4 + 1] = (b32[i] shr 8 and 0xff).toByte() + bArray[i * 4 + 2] = (b32[i] shr 16 and 0xff).toByte() + bArray[i * 4 + 3] = (b32[i] shr 24 and 0xff).toByte() + i++ + } + } + + fun blockxor( + sArray: ByteArray, + si: Int, + dArray: ByteArray, + di: Int, + len: Int, + ) { + for (i in 0..It can be used to construct a SecretKey from a byte array, - * without having to go through a (provider-based) - * SecretKeyFactory. - * - *

This class is only useful for raw secret keys that can be represented as - * a byte array and have no key parameters associated with them, e.g., DES or - * Triple DES keys. - * - * @author Jan Luehe - * - * @see javax.crypto.SecretKey - * @see javax.crypto.SecretKeyFactory - * @since 1.4 - */ -public class SecretKeyOrEmptySpec implements KeySpec, SecretKey { - - private static final long serialVersionUID = 6577238317307289933L; - - /** - * The secret key. - * - * @serial - */ - private byte[] key; - - /** - * The name of the algorithm associated with this key. - * - * @serial - */ - private String algorithm; - - /** - * Constructs a secret key from the given byte array. - * - *

This constructor does not check if the given bytes indeed specify a - * secret key of the specified algorithm. For example, if the algorithm is - * DES, this constructor does not check if key is 8 bytes - * long, and also does not check for weak or semi-weak keys. - * In order for those checks to be performed, an algorithm-specific - * key specification class (in this case: - * {@link DESKeySpec DESKeySpec}) - * should be used. - * - * @param key the key material of the secret key. The contents of - * the array are copied to protect against subsequent modification. - * @param algorithm the name of the secret-key algorithm to be associated - * with the given key material. - * See Appendix A in the - * Java Cryptography Architecture Reference Guide - * for information about standard algorithm names. - * @exception IllegalArgumentException if algorithm - * is null or key is null or empty. - */ - public SecretKeyOrEmptySpec(byte[] key, String algorithm) { - if (key == null || algorithm == null) { - throw new IllegalArgumentException("Missing argument"); - } - this.key = key.clone(); - this.algorithm = algorithm; - } - - /** - * Constructs a secret key from the given byte array, using the first - * len bytes of key, starting at - * offset inclusive. - * - *

The bytes that constitute the secret key are - * those between key[offset] and - * key[offset+len-1] inclusive. - * - *

This constructor does not check if the given bytes indeed specify a - * secret key of the specified algorithm. For example, if the algorithm is - * DES, this constructor does not check if key is 8 bytes - * long, and also does not check for weak or semi-weak keys. - * In order for those checks to be performed, an algorithm-specific key - * specification class (in this case: - * {@link DESKeySpec DESKeySpec}) - * must be used. - * - * @param key the key material of the secret key. The first - * len bytes of the array beginning at - * offset inclusive are copied to protect - * against subsequent modification. - * @param offset the offset in key where the key material - * starts. - * @param len the length of the key material. - * @param algorithm the name of the secret-key algorithm to be associated - * with the given key material. - * See Appendix A in the - * Java Cryptography Architecture Reference Guide - * for information about standard algorithm names. - * @exception IllegalArgumentException if algorithm - * is null or key is null, empty, or too short, - * i.e. {@code key.length-offsetoffset or len index bytes outside the - * key. - */ - public SecretKeyOrEmptySpec(byte[] key, int offset, int len, String algorithm) { - if (key == null || algorithm == null) { - throw new IllegalArgumentException("Missing argument"); - } - if (key.length-offset < len) { - throw new IllegalArgumentException - ("Invalid offset/length combination"); - } - if (len < 0) { - throw new ArrayIndexOutOfBoundsException("len is negative"); - } - this.key = new byte[len]; - System.arraycopy(key, offset, this.key, 0, len); - this.algorithm = algorithm; - } - - /** - * Returns the name of the algorithm associated with this secret key. - * - * @return the secret key algorithm. - */ - public String getAlgorithm() { - return this.algorithm; - } - - /** - * Returns the name of the encoding format for this secret key. - * - * @return the string "RAW". - */ - public String getFormat() { - return "RAW"; - } - - /** - * Returns the key material of this secret key. - * - * @return the key material. Returns a new array - * each time this method is called. - */ - public byte[] getEncoded() { - return this.key.clone(); - } - - /** - * Calculates a hash code value for the object. - * Objects that are equal will also have the same hashcode. - */ - public int hashCode() { - int retval = 0; - for (int i = 1; i < this.key.length; i++) { - retval += this.key[i] * i; - } - if (this.algorithm.equalsIgnoreCase("TripleDES")) - return (retval ^= "desede".hashCode()); - else - return (retval ^= - this.algorithm.toLowerCase(Locale.ENGLISH).hashCode()); - } - - /** - * Tests for equality between the specified object and this - * object. Two SecretKeySpec objects are considered equal if - * they are both SecretKey instances which have the - * same case-insensitive algorithm name and key encoding. - * - * @param obj the object to test for equality with this object. - * - * @return true if the objects are considered equal, false if - * obj is null or otherwise. - */ - public boolean equals(Object obj) { - if (this == obj) - return true; - - if (!(obj instanceof SecretKey)) - return false; - - String thatAlg = ((SecretKey)obj).getAlgorithm(); - if (!(thatAlg.equalsIgnoreCase(this.algorithm))) { - if ((!(thatAlg.equalsIgnoreCase("DESede")) - || !(this.algorithm.equalsIgnoreCase("TripleDES"))) - && (!(thatAlg.equalsIgnoreCase("TripleDES")) - || !(this.algorithm.equalsIgnoreCase("DESede")))) - return false; - } - - byte[] thatKey = ((SecretKey)obj).getEncoded(); - - return MessageDigest.isEqual(this.key, thatKey); - } -} diff --git a/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip49PrivKeyEnc/SecretKeyOrEmptySpec.kt b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip49PrivKeyEnc/SecretKeyOrEmptySpec.kt new file mode 100644 index 000000000..a5cc12e25 --- /dev/null +++ b/quartz/src/androidMain/kotlin/com/vitorpamplona/quartz/nip49PrivKeyEnc/SecretKeyOrEmptySpec.kt @@ -0,0 +1,180 @@ +/** + * Copyright (c) 2025 Vitor Pamplona + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the + * Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package com.vitorpamplona.quartz.nip49PrivKeyEnc + +import java.security.MessageDigest +import java.security.spec.KeySpec +import javax.crypto.SecretKey + +/** + * This class specifies a secret key in a provider-independent fashion. + * + * + * It can be used to construct a `SecretKey` from a byte array, + * without having to go through a (provider-based) + * `SecretKeyFactory`. + * + * + * This class is only useful for raw secret keys that can be represented as + * a byte array and have no key parameters associated with them, e.g., DES or + * Triple DES keys. + * + * @author Jan Luehe + * + * @see SecretKey + * + * @see javax.crypto.SecretKeyFactory + * + * @since 1.4 + */ +class SecretKeyOrEmptySpec : + KeySpec, + SecretKey { + /** + * The secret key. + * + * @serial + */ + private val key: ByteArray + + /** + * The name of the algorithm associated with this key. + * + * @serial + */ + private val algorithm: String + + /** + * Constructs a secret key from the given byte array. + * + * + * This constructor does not check if the given bytes indeed specify a + * secret key of the specified algorithm. For example, if the algorithm is + * DES, this constructor does not check if `key` is 8 bytes + * long, and also does not check for weak or semi-weak keys. + * In order for those checks to be performed, an algorithm-specific + * *key specification* class + * + * @param key the key material of the secret key. The contents of + * the array are copied to protect against subsequent modification. + * @param algorithm the name of the secret-key algorithm to be associated + * with the given key material. + * See Appendix A in the [ + * Java Cryptography Architecture Reference Guide]({@docRoot}/../technotes/guides/security/crypto/CryptoSpec.html#AppA) + * for information about standard algorithm names. + * @exception IllegalArgumentException if `algorithm` + * is null or `key` is null or empty. + */ + constructor(key: ByteArray, algorithm: String) { + this.key = key.clone() + this.algorithm = algorithm + } + + /** + * Returns the name of the algorithm associated with this secret key. + * + * @return the secret key algorithm. + */ + override fun getAlgorithm(): String = this.algorithm + + /** + * Returns the name of the encoding format for this secret key. + * + * @return the string "RAW". + */ + override fun getFormat(): String = "RAW" + + /** + * Returns the key material of this secret key. + * + * @return the key material. Returns a new array + * each time this method is called. + */ + override fun getEncoded(): ByteArray = this.key.clone() + + /** + * Calculates a hash code value for the object. + * Objects that are equal will also have the same hashcode. + */ + override fun hashCode(): Int { + var retval = 0 + for (i in 1..