diff --git a/NOTICE b/NOTICE index d8ee885..734ee8b 100644 --- a/NOTICE +++ b/NOTICE @@ -19,6 +19,7 @@ This project includes: ASM Tree under BSD ASM Util under BSD Gson under Apache 2.0 + HKDF-RFC5869 under Apache License, Version 2.0 jffi under The Apache Software License, Version 2.0 jnr-a64asm under The Apache Software License, Version 2.0 jnr-ffi under The Apache Software License, Version 2.0 diff --git a/README.md b/README.md index 129fca5..821a86d 100644 --- a/README.md +++ b/README.md @@ -202,18 +202,18 @@ If you're building a (non-Android) JDK project, you will want to define the foll dev.paseto jpaseto-api - 0.1.0 + 0.5.0 dev.paseto jpaseto-impl - 0.1.0 + 0.5.0 runtime dev.paseto jpaseto-jackson - 0.1.0 + 0.5.0 runtime @@ -221,7 +221,15 @@ If you're building a (non-Android) JDK project, you will want to define the foll dev.paseto jpaseto-bouncy-castle - 0.1.0 + 0.5.0 + runtime + --> + + @@ -230,7 +238,7 @@ If you're building a (non-Android) JDK project, you will want to define the foll dev.paseto jpaseto-sodium - 0.1.0 + 0.5.0 runtime --> ``` @@ -240,14 +248,16 @@ If you're building a (non-Android) JDK project, you will want to define the foll ```groovy dependencies { - compile 'dev.paseto:jpaseto-api:0.1.0' - runtime 'dev.paseto:jpaseto-impl:0.1.0', + compile 'dev.paseto:jpaseto-api:0.5.0' + runtime 'dev.paseto:jpaseto-impl:0.5.0', // Uncomment the next lines if you want to use v1.local tokens - // 'dev.paseto:jpaseto-bouncy-castle:0.1.0', + // 'dev.paseto:jpaseto-bouncy-castle:0.5.0', + // or this (only 'v1.local' tokens) for smaller dependency (~11 KB for HKDF vs. ~4.3 MB for Bouncy Castle) + // 'dev.paseto:jpaseto-hkdf:0.5.0', // Uncomment the next lines if you want to use v2 tokens // NOTE: this requires the native lib sodium library installed on your system see below - // 'dev.paseto:jpaseto-sodium:0.1.0', - 'dev.paseto:jpaseto-jackson:0.1.0' + // 'dev.paseto:jpaseto-sodium:0.5.0', + 'dev.paseto:jpaseto-jackson:0.5.0' } ``` @@ -255,7 +265,7 @@ dependencies { Installation the a native library [libsodium](https://github.com/jedisct1/libsodium) is required when creating or parseing "v2.local" tokens. -**NOTE:** `public` tokens can be used with the `jpaseto-bouncy-castle` dependency or Java 11+. `v1.local` tokens require `jpaseto-bouncy-castle`. +**NOTE:** `public` tokens can be used with the `jpaseto-bouncy-castle` dependency or Java 11+. `v1.local` tokens require `jpaseto-bouncy-castle` or `jpaseto-hkdf`. - MacOS - Can install libsodium using brew: diff --git a/api/pom.xml b/api/pom.xml index 91fc0e2..7ce769d 100644 --- a/api/pom.xml +++ b/api/pom.xml @@ -20,7 +20,7 @@ dev.paseto jpaseto-root - 0.4.1-SNAPSHOT + 0.5.0-SNAPSHOT jpaseto-api diff --git a/coverage/pom.xml b/coverage/pom.xml index 0f0e83c..eefbbcc 100644 --- a/coverage/pom.xml +++ b/coverage/pom.xml @@ -21,7 +21,7 @@ dev.paseto jpaseto-root - 0.4.1-SNAPSHOT + 0.5.0-SNAPSHOT jpaseto-coverage @@ -49,6 +49,10 @@ dev.paseto jpaseto-bouncy-castle + + dev.paseto + jpaseto-hkdf + dev.paseto jpaseto-its-sodium-jackson diff --git a/extensions/crypto/bouncy-castle/pom.xml b/extensions/crypto/bouncy-castle/pom.xml index fca44e8..479020c 100644 --- a/extensions/crypto/bouncy-castle/pom.xml +++ b/extensions/crypto/bouncy-castle/pom.xml @@ -20,7 +20,7 @@ dev.paseto jpaseto-root - 0.4.1-SNAPSHOT + 0.5.0-SNAPSHOT ../../../pom.xml diff --git a/extensions/crypto/bouncy-castle/src/main/java/dev/paseto/jpaseto/crypto/bouncycastle/BouncyCastleV1LocalCryptoProvider.java b/extensions/crypto/bouncy-castle/src/main/java/dev/paseto/jpaseto/crypto/bouncycastle/BouncyCastleV1LocalCryptoProvider.java index bcc90c6..596fd31 100644 --- a/extensions/crypto/bouncy-castle/src/main/java/dev/paseto/jpaseto/crypto/bouncycastle/BouncyCastleV1LocalCryptoProvider.java +++ b/extensions/crypto/bouncy-castle/src/main/java/dev/paseto/jpaseto/crypto/bouncycastle/BouncyCastleV1LocalCryptoProvider.java @@ -16,93 +16,23 @@ package dev.paseto.jpaseto.crypto.bouncycastle; import com.google.auto.service.AutoService; -import dev.paseto.jpaseto.InvalidMacException; -import dev.paseto.jpaseto.impl.crypto.Hmacs; -import dev.paseto.jpaseto.impl.crypto.PreAuthEncoder; +import dev.paseto.jpaseto.impl.crypto.BaseV1LocalCryptoProvider; import dev.paseto.jpaseto.impl.crypto.V1LocalCryptoProvider; -import dev.paseto.jpaseto.impl.lang.Bytes; import org.bouncycastle.crypto.generators.HKDFBytesGenerator; import org.bouncycastle.crypto.params.HKDFParameters; import org.bouncycastle.crypto.util.DigestFactory; -import javax.crypto.Cipher; import javax.crypto.SecretKey; -import java.security.MessageDigest; -import java.util.Arrays; - -import static java.nio.charset.StandardCharsets.UTF_8; @AutoService(V1LocalCryptoProvider.class) -public class BouncyCastleV1LocalCryptoProvider implements V1LocalCryptoProvider { - - private static final byte[] HEADER_BYTES = "v1.local.".getBytes(UTF_8); +public class BouncyCastleV1LocalCryptoProvider extends BaseV1LocalCryptoProvider { @Override - public byte[] encrypt(byte[] payload, byte[] footer, byte[] nonce, SecretKey sharedSecret) { - - byte[] salt = Arrays.copyOf(nonce, 16); - byte[] rightNonce = Arrays.copyOfRange(nonce, 16, nonce.length); - - // 4 - byte[] encryptionKey = encryptionKey(sharedSecret, salt); - byte[] authenticationKey = authenticationKey(sharedSecret, salt); - - // 5 - byte[] cipherText = V1LocalCryptoProvider.doCipher(Cipher.ENCRYPT_MODE, encryptionKey, rightNonce, payload); - - //6 - byte[] preAuth = PreAuthEncoder.encode(HEADER_BYTES, nonce, cipherText, footer); - - //7 - byte[] calculatedMac = Hmacs.hmacSha384(authenticationKey, preAuth); - - // 8 - return Bytes.concat(nonce, cipherText, calculatedMac); - } - - @Override - public byte[] decrypt(byte[] encryptedBytes, byte[] footer, byte[] nonce, SecretKey sharedSecret) { - - // 3 - byte[] salt = Arrays.copyOf(nonce, 16); - byte[] rightNonce = Arrays.copyOfRange(nonce, 16, nonce.length); - - byte[] cipherText = Arrays.copyOfRange(encryptedBytes, 32, encryptedBytes.length - 48); - byte[] mac = Arrays.copyOfRange(encryptedBytes, encryptedBytes.length - 48, encryptedBytes.length); - - // 4 - byte[] encryptionKey = encryptionKey(sharedSecret, salt); - byte[] authenticationKey = authenticationKey(sharedSecret, salt); - - // 5 - byte[] preAuth = PreAuthEncoder.encode(HEADER_BYTES, nonce, cipherText, footer); - - // 6 - byte[] calculatedMac = Hmacs.hmacSha384(authenticationKey, preAuth); - - // 7 - if (!MessageDigest.isEqual(calculatedMac, mac)) { - throw new InvalidMacException("Failed to validate mac in token"); - } - - // 8 - return V1LocalCryptoProvider.doCipher(Cipher.DECRYPT_MODE, encryptionKey, rightNonce, cipherText); - } - - private byte[] hkdfSha384(SecretKey sharedSecret, byte[] salt, String info) { - + protected byte[] hkdfSha384(SecretKey sharedSecret, byte[] salt, byte[] info) { HKDFBytesGenerator hkdfBytesGenerator = new HKDFBytesGenerator(DigestFactory.createSHA384()); - hkdfBytesGenerator.init(new HKDFParameters(sharedSecret.getEncoded(), salt, info.getBytes(UTF_8))); + hkdfBytesGenerator.init(new HKDFParameters(sharedSecret.getEncoded(), salt, info)); byte[] result = new byte[32]; hkdfBytesGenerator.generateBytes(result, 0, result.length); return result; } - - private byte[] encryptionKey(SecretKey sharedSecret, byte[] salt) { - return hkdfSha384(sharedSecret, salt, "paseto-encryption-key"); - } - - private byte[] authenticationKey(SecretKey sharedSecret, byte[] salt) { - return hkdfSha384(sharedSecret, salt, "paseto-auth-key-for-aead"); - } } diff --git a/extensions/crypto/bouncy-castle/src/test/groovy/dev/paseto/jpaseto/crypto/bouncycastle/BouncyCastleV1LocalCryptoProviderTest.groovy b/extensions/crypto/bouncy-castle/src/test/groovy/dev/paseto/jpaseto/crypto/bouncycastle/BouncyCastleV1LocalCryptoProviderTest.groovy new file mode 100644 index 0000000..d920389 --- /dev/null +++ b/extensions/crypto/bouncy-castle/src/test/groovy/dev/paseto/jpaseto/crypto/bouncycastle/BouncyCastleV1LocalCryptoProviderTest.groovy @@ -0,0 +1,54 @@ +/* + * Copyright 2020-Present paseto.dev + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package dev.paseto.jpaseto.crypto.bouncycastle + +import dev.paseto.jpaseto.impl.crypto.V1LocalCryptoProvider +import dev.paseto.jpaseto.lang.Keys +import dev.paseto.jpaseto.lang.Services +import org.testng.annotations.Test + +import javax.crypto.SecretKey +import java.nio.charset.StandardCharsets + +import static org.hamcrest.MatcherAssert.assertThat +import static org.hamcrest.Matchers.equalTo +import static org.hamcrest.Matchers.instanceOf +class BouncyCastleV1LocalCryptoProviderTest { + + @Test + void loadServiceTest() { + assertThat Services.loadFirst(V1LocalCryptoProvider), instanceOf(BouncyCastleV1LocalCryptoProvider) + } + + @Test + void hkdfSha384Test() { + SecretKey secretKey = Keys.secretKey(decode("3nQBDXcLZRTcVZF0NS/6yZ3JO03i/Yv+C1CQRvPgmJk")) + byte[] salt = decode("/bvrxpG04bMH2j98Sgm5ug") + byte[] info = "test-info".getBytes(StandardCharsets.UTF_8) + String expectedResult = "PtiIWzWkNywvjlnyv60Rtz2Zr7vQsgZivlj0Ys9HDy4" + + byte[] result = new BouncyCastleV1LocalCryptoProvider().hkdfSha384(secretKey, salt, info) + assertThat encodeToString(result), equalTo(expectedResult) + } + + private static String encodeToString(byte[] bytes) { + return Base64.getEncoder().withoutPadding().encodeToString(bytes) + } + + private static byte[] decode(String input) { + return Base64.getDecoder().decode(input) + } +} diff --git a/extensions/crypto/hkdf/pom.xml b/extensions/crypto/hkdf/pom.xml new file mode 100644 index 0000000..26d25c5 --- /dev/null +++ b/extensions/crypto/hkdf/pom.xml @@ -0,0 +1,49 @@ + + + + 4.0.0 + + + dev.paseto + jpaseto-root + 0.5.0-SNAPSHOT + ../../../pom.xml + + + jpaseto-hkdf + JPaseto :: Crypto :: HKDF + + + + dev.paseto + jpaseto-impl + + + + at.favre.lib + hkdf + + + + com.google.auto.service + auto-service + provided + true + + + + \ No newline at end of file diff --git a/extensions/crypto/hkdf/src/main/java/dev/paseto/jpaseto/crypto/hkdf/HKDFV1LocalCryptoProvider.java b/extensions/crypto/hkdf/src/main/java/dev/paseto/jpaseto/crypto/hkdf/HKDFV1LocalCryptoProvider.java new file mode 100644 index 0000000..fc34fa3 --- /dev/null +++ b/extensions/crypto/hkdf/src/main/java/dev/paseto/jpaseto/crypto/hkdf/HKDFV1LocalCryptoProvider.java @@ -0,0 +1,37 @@ +/* + * Copyright 2020-Present paseto.dev + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package dev.paseto.jpaseto.crypto.hkdf; + +import at.favre.lib.crypto.HKDF; +import at.favre.lib.crypto.HkdfMacFactory; +import com.google.auto.service.AutoService; +import dev.paseto.jpaseto.impl.crypto.BaseV1LocalCryptoProvider; +import dev.paseto.jpaseto.impl.crypto.V1LocalCryptoProvider; + +import javax.crypto.SecretKey; + +/** + * @since 0.5.0 + */ +@AutoService(V1LocalCryptoProvider.class) +public class HKDFV1LocalCryptoProvider extends BaseV1LocalCryptoProvider { + + @Override + protected byte[] hkdfSha384(SecretKey sharedSecret, byte[] salt, byte[] info) { + HKDF hkdfSha384 = HKDF.from(new HkdfMacFactory.Default("HmacSHA384")); + return hkdfSha384.extractAndExpand(salt, sharedSecret.getEncoded(), info, 32); + } +} diff --git a/extensions/crypto/hkdf/src/test/groovy/dev/paseto/jpaseto/crypto/hkdf/HKDFV1LocalCryptoProviderTest.groovy b/extensions/crypto/hkdf/src/test/groovy/dev/paseto/jpaseto/crypto/hkdf/HKDFV1LocalCryptoProviderTest.groovy new file mode 100644 index 0000000..510b146 --- /dev/null +++ b/extensions/crypto/hkdf/src/test/groovy/dev/paseto/jpaseto/crypto/hkdf/HKDFV1LocalCryptoProviderTest.groovy @@ -0,0 +1,55 @@ +/* + * Copyright 2020-Present paseto.dev + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package dev.paseto.jpaseto.crypto.hkdf + +import dev.paseto.jpaseto.impl.crypto.V1LocalCryptoProvider +import dev.paseto.jpaseto.lang.Keys +import dev.paseto.jpaseto.lang.Services +import org.testng.annotations.Test + +import javax.crypto.SecretKey +import java.nio.charset.StandardCharsets + +import static org.hamcrest.MatcherAssert.assertThat +import static org.hamcrest.Matchers.equalTo +import static org.hamcrest.Matchers.instanceOf + +class HKDFV1LocalCryptoProviderTest { + + @Test + void loadServiceTest() { + assertThat Services.loadFirst(V1LocalCryptoProvider), instanceOf(HKDFV1LocalCryptoProvider) + } + + @Test + void hkdfSha384Test() { + SecretKey secretKey = Keys.secretKey(decode("3nQBDXcLZRTcVZF0NS/6yZ3JO03i/Yv+C1CQRvPgmJk")) + byte[] salt = decode("/bvrxpG04bMH2j98Sgm5ug") + byte[] info = "test-info".getBytes(StandardCharsets.UTF_8) + String expectedResult = "PtiIWzWkNywvjlnyv60Rtz2Zr7vQsgZivlj0Ys9HDy4" + + byte[] result = new HKDFV1LocalCryptoProvider().hkdfSha384(secretKey, salt, info) + assertThat encodeToString(result), equalTo(expectedResult) + } + + private static String encodeToString(byte[] bytes) { + return Base64.getEncoder().withoutPadding().encodeToString(bytes) + } + + private static byte[] decode(String input) { + return Base64.getDecoder().decode(input) + } +} diff --git a/extensions/crypto/sodium/pom.xml b/extensions/crypto/sodium/pom.xml index 309fd1b..4e1458e 100644 --- a/extensions/crypto/sodium/pom.xml +++ b/extensions/crypto/sodium/pom.xml @@ -20,7 +20,7 @@ dev.paseto jpaseto-root - 0.4.1-SNAPSHOT + 0.5.0-SNAPSHOT ../../../pom.xml diff --git a/extensions/json/gson/pom.xml b/extensions/json/gson/pom.xml index 58410c3..453163d 100644 --- a/extensions/json/gson/pom.xml +++ b/extensions/json/gson/pom.xml @@ -21,7 +21,7 @@ dev.paseto jpaseto-root - 0.4.1-SNAPSHOT + 0.5.0-SNAPSHOT ../../../pom.xml diff --git a/extensions/json/jackson/pom.xml b/extensions/json/jackson/pom.xml index 9f34747..83d5ee4 100644 --- a/extensions/json/jackson/pom.xml +++ b/extensions/json/jackson/pom.xml @@ -20,7 +20,7 @@ dev.paseto jpaseto-root - 0.4.1-SNAPSHOT + 0.5.0-SNAPSHOT ../../../pom.xml diff --git a/impl/pom.xml b/impl/pom.xml index cf53455..af3678c 100644 --- a/impl/pom.xml +++ b/impl/pom.xml @@ -20,7 +20,7 @@ dev.paseto jpaseto-root - 0.4.1-SNAPSHOT + 0.5.0-SNAPSHOT jpaseto-impl diff --git a/impl/src/main/java/dev/paseto/jpaseto/impl/crypto/BaseV1LocalCryptoProvider.java b/impl/src/main/java/dev/paseto/jpaseto/impl/crypto/BaseV1LocalCryptoProvider.java new file mode 100644 index 0000000..2ed5cf5 --- /dev/null +++ b/impl/src/main/java/dev/paseto/jpaseto/impl/crypto/BaseV1LocalCryptoProvider.java @@ -0,0 +1,97 @@ +/* + * Copyright 2019-Present paseto.dev + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package dev.paseto.jpaseto.impl.crypto; + +import dev.paseto.jpaseto.InvalidMacException; +import dev.paseto.jpaseto.impl.lang.Bytes; + +import javax.crypto.Cipher; +import javax.crypto.SecretKey; +import java.security.MessageDigest; +import java.util.Arrays; + +import static java.nio.charset.StandardCharsets.UTF_8; + +public abstract class BaseV1LocalCryptoProvider implements V1LocalCryptoProvider { + + private static final byte[] HEADER_BYTES = "v1.local.".getBytes(UTF_8); + + @Override + public byte[] encrypt(byte[] payload, byte[] footer, byte[] nonce, SecretKey sharedSecret) { + + byte[] salt = Arrays.copyOf(nonce, 16); + byte[] rightNonce = Arrays.copyOfRange(nonce, 16, nonce.length); + + // 4 + byte[] encryptionKey = encryptionKey(sharedSecret, salt); + byte[] authenticationKey = authenticationKey(sharedSecret, salt); + + // 5 + byte[] cipherText = V1LocalCryptoProvider.doCipher(Cipher.ENCRYPT_MODE, encryptionKey, rightNonce, payload); + + //6 + byte[] preAuth = PreAuthEncoder.encode(HEADER_BYTES, nonce, cipherText, footer); + + //7 + byte[] calculatedMac = Hmacs.hmacSha384(authenticationKey, preAuth); + + // 8 + return Bytes.concat(nonce, cipherText, calculatedMac); + } + + @Override + public byte[] decrypt(byte[] encryptedBytes, byte[] footer, byte[] nonce, SecretKey sharedSecret) { + + // 3 + byte[] salt = Arrays.copyOf(nonce, 16); + byte[] rightNonce = Arrays.copyOfRange(nonce, 16, nonce.length); + + byte[] cipherText = Arrays.copyOfRange(encryptedBytes, 32, encryptedBytes.length - 48); + byte[] mac = Arrays.copyOfRange(encryptedBytes, encryptedBytes.length - 48, encryptedBytes.length); + + // 4 + byte[] encryptionKey = encryptionKey(sharedSecret, salt); + byte[] authenticationKey = authenticationKey(sharedSecret, salt); + + // 5 + byte[] preAuth = PreAuthEncoder.encode(HEADER_BYTES, nonce, cipherText, footer); + + // 6 + byte[] calculatedMac = Hmacs.hmacSha384(authenticationKey, preAuth); + + // 7 + if (!MessageDigest.isEqual(calculatedMac, mac)) { + throw new InvalidMacException("Failed to validate mac in token"); + } + + // 8 + return V1LocalCryptoProvider.doCipher(Cipher.DECRYPT_MODE, encryptionKey, rightNonce, cipherText); + } + + private byte[] encryptionKey(SecretKey sharedSecret, byte[] salt) { + return hkdfSha384(sharedSecret, salt, "paseto-encryption-key"); + } + + private byte[] authenticationKey(SecretKey sharedSecret, byte[] salt) { + return hkdfSha384(sharedSecret, salt, "paseto-auth-key-for-aead"); + } + + private byte[] hkdfSha384(SecretKey sharedSecret, byte[] salt, String info) { + return hkdfSha384(sharedSecret, salt, info.getBytes(UTF_8)); + } + + protected abstract byte[] hkdfSha384(SecretKey sharedSecret, byte[] salt, byte[] info); +} diff --git a/integration-tests/pom.xml b/integration-tests/pom.xml index 8325933..5e9c603 100644 --- a/integration-tests/pom.xml +++ b/integration-tests/pom.xml @@ -20,7 +20,7 @@ dev.paseto jpaseto-root - 0.4.1-SNAPSHOT + 0.5.0-SNAPSHOT jpaseto-its-sodium-jackson @@ -46,6 +46,11 @@ jpaseto-bouncy-castle runtime + + dev.paseto + jpaseto-hkdf + runtime + dev.paseto jpaseto-jackson diff --git a/pom.xml b/pom.xml index b1db0f7..7db523c 100644 --- a/pom.xml +++ b/pom.xml @@ -25,7 +25,7 @@ dev.paseto jpaseto-root - 0.4.1-SNAPSHOT + 0.5.0-SNAPSHOT pom JPaseto @@ -68,6 +68,7 @@ impl extensions/crypto/sodium extensions/crypto/bouncy-castle + extensions/crypto/hkdf extensions/json/jackson extensions/json/gson integration-tests @@ -79,33 +80,38 @@ dev.paseto jpaseto-api - 0.4.1-SNAPSHOT + 0.5.0-SNAPSHOT dev.paseto jpaseto-impl - 0.4.1-SNAPSHOT + 0.5.0-SNAPSHOT dev.paseto jpaseto-jackson - 0.4.1-SNAPSHOT + 0.5.0-SNAPSHOT dev.paseto jpaseto-sodium - 0.4.1-SNAPSHOT + 0.5.0-SNAPSHOT dev.paseto jpaseto-bouncy-castle - 0.4.1-SNAPSHOT + 0.5.0-SNAPSHOT + + + dev.paseto + jpaseto-hkdf + 0.5.0-SNAPSHOT dev.paseto jpaseto-its-sodium-jackson test - 0.4.1-SNAPSHOT + 0.5.0-SNAPSHOT @@ -120,6 +126,12 @@ 1.64 + + at.favre.lib + hkdf + 1.1.0 + +