From 900fa46911412a30f36d8e7a479f577a0575bd26 Mon Sep 17 00:00:00 2001 From: cj Date: Mon, 16 Sep 2024 23:31:41 +0800 Subject: [PATCH 1/5] version: 1.6.1 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index d9ef478..48c176a 100644 --- a/pom.xml +++ b/pom.xml @@ -46,7 +46,7 @@ false - 1.7-SNAPSHOT + 1.6.1 UTF-8 UTF-8 1.8 From ec513c4f0ba5f688417c26c184ce3821437d58df Mon Sep 17 00:00:00 2001 From: cj Date: Mon, 16 Sep 2024 23:33:21 +0800 Subject: [PATCH 2/5] chore: Update develop version --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 48c176a..d9ef478 100644 --- a/pom.xml +++ b/pom.xml @@ -46,7 +46,7 @@ false - 1.6.1 + 1.7-SNAPSHOT UTF-8 UTF-8 1.8 From d8d8cd047a09699e6f04ea9c446e97c70caec604 Mon Sep 17 00:00:00 2001 From: cj Date: Sat, 14 Sep 2024 16:29:02 +0800 Subject: [PATCH 3/5] fix: Fix broken imports --- .../java/com/power4j/tile/crypto/core/BlockCipher.java | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/tile-crypto/src/main/java/com/power4j/tile/crypto/core/BlockCipher.java b/tile-crypto/src/main/java/com/power4j/tile/crypto/core/BlockCipher.java index 4bbb85c..3cb37ba 100644 --- a/tile-crypto/src/main/java/com/power4j/tile/crypto/core/BlockCipher.java +++ b/tile-crypto/src/main/java/com/power4j/tile/crypto/core/BlockCipher.java @@ -16,8 +16,7 @@ package com.power4j.tile.crypto.core; -import com.power4j.tile.crypto.wrapper.HexDecoder; -import com.power4j.tile.crypto.wrapper.HexEncoder; +import com.power4j.tile.crypto.core.encode.HexEncoder; import com.power4j.tile.crypto.wrapper.InputDecoder; import com.power4j.tile.crypto.wrapper.OutputEncoder; @@ -119,7 +118,7 @@ default R decryptWith(InputDecoder decoder, OutputEncoder encoder, * @see TextCipherBuilder */ default String encryptHex(byte[] data) throws GeneralCryptoException { - return encryptWith(InputDecoder.NO_OP, HexEncoder.DEFAULT, data); + return encryptWith(InputDecoder.NO_OP, HexEncoder.DEFAULT::encode, data); } /** @@ -131,7 +130,7 @@ default String encryptHex(byte[] data) throws GeneralCryptoException { * @see TextCipherBuilder */ default byte[] decryptHex(String data) throws GeneralCryptoException { - return decryptWith(HexDecoder.DEFAULT, OutputEncoder.NO_OP, data); + return decryptWith(HexEncoder.DEFAULT::decode, OutputEncoder.NO_OP, data); } } From 5c7c3358e1e2a67cff557d84b2baa29cfabae417 Mon Sep 17 00:00:00 2001 From: cj Date: Sat, 14 Sep 2024 17:27:14 +0800 Subject: [PATCH 4/5] feat: Support time based dynamic key decryption --- .../tile/crypto/dynamic/DecryptInfo.java | 2 +- .../crypto/dynamic/DynamicDecryptBuilder.java | 12 +- .../tile/crypto/dynamic/DynamicKey.java | 2 +- .../power4j/tile/crypto/dynamic/Pools.java | 4 +- .../crypto/dynamic/SimpleDynamicDecrypt.java | 7 +- .../tile/crypto/dynamic/TimeBasedPool.java | 149 ++++++++++++++++++ .../dynamic/SimpleDynamicDecryptTest.java | 40 ++++- .../crypto/dynamic/TimeBasedPoolTest.java | 83 ++++++++++ 8 files changed, 291 insertions(+), 8 deletions(-) create mode 100644 tile-crypto/src/main/java/com/power4j/tile/crypto/dynamic/TimeBasedPool.java create mode 100644 tile-crypto/src/test/java/com/power4j/tile/crypto/dynamic/TimeBasedPoolTest.java diff --git a/tile-crypto/src/main/java/com/power4j/tile/crypto/dynamic/DecryptInfo.java b/tile-crypto/src/main/java/com/power4j/tile/crypto/dynamic/DecryptInfo.java index 86b9c07..218e691 100644 --- a/tile-crypto/src/main/java/com/power4j/tile/crypto/dynamic/DecryptInfo.java +++ b/tile-crypto/src/main/java/com/power4j/tile/crypto/dynamic/DecryptInfo.java @@ -29,7 +29,7 @@ public class DecryptInfo { private final boolean matched; - private final long keyIndex; + private final String keyTag; private final byte[] data; diff --git a/tile-crypto/src/main/java/com/power4j/tile/crypto/dynamic/DynamicDecryptBuilder.java b/tile-crypto/src/main/java/com/power4j/tile/crypto/dynamic/DynamicDecryptBuilder.java index 92e091e..5130884 100644 --- a/tile-crypto/src/main/java/com/power4j/tile/crypto/dynamic/DynamicDecryptBuilder.java +++ b/tile-crypto/src/main/java/com/power4j/tile/crypto/dynamic/DynamicDecryptBuilder.java @@ -21,6 +21,7 @@ import org.springframework.lang.Nullable; import java.util.function.Function; +import java.util.function.Supplier; /** * @author CJ (power4j@outlook.com) @@ -40,6 +41,8 @@ public class DynamicDecryptBuilder { private Function checksumCalculator; + private Supplier paramterSupplier; + public DynamicDecryptBuilder(String algorithmName, String mode, String padding) { this.algorithmName = algorithmName; this.mode = mode; @@ -85,6 +88,11 @@ public DynamicDecryptBuilder checksumCalculator(Function checksu return this; } + public DynamicDecryptBuilder parameterSupplier(Supplier supplier) { + this.paramterSupplier = supplier; + return this; + } + public SimpleDynamicDecrypt simple() { Validate.notEmpty(algorithmName, "algorithmName must not be empty"); @@ -93,8 +101,10 @@ public SimpleDynamicDecrypt simple() { Validate.notNull(keyPool, "keyPool must not be null"); Validate.notNull(checksumCalculator, "checksumCalculator must not be null"); + Supplier paramSupplier = paramterSupplier == null ? () -> 0L : paramterSupplier; + return new SimpleDynamicDecrypt(algorithmName, mode, padding, keyPool, ivPool == null ? Pools.empty() : ivPool, - checksumCalculator); + checksumCalculator, paramSupplier); } diff --git a/tile-crypto/src/main/java/com/power4j/tile/crypto/dynamic/DynamicKey.java b/tile-crypto/src/main/java/com/power4j/tile/crypto/dynamic/DynamicKey.java index 7a42552..df98aa5 100644 --- a/tile-crypto/src/main/java/com/power4j/tile/crypto/dynamic/DynamicKey.java +++ b/tile-crypto/src/main/java/com/power4j/tile/crypto/dynamic/DynamicKey.java @@ -27,7 +27,7 @@ @RequiredArgsConstructor public class DynamicKey { - private final long index; + private final String tag; private final byte[] key; diff --git a/tile-crypto/src/main/java/com/power4j/tile/crypto/dynamic/Pools.java b/tile-crypto/src/main/java/com/power4j/tile/crypto/dynamic/Pools.java index e5c07e0..b087ab4 100644 --- a/tile-crypto/src/main/java/com/power4j/tile/crypto/dynamic/Pools.java +++ b/tile-crypto/src/main/java/com/power4j/tile/crypto/dynamic/Pools.java @@ -44,7 +44,7 @@ public KeyPool fixed(long id, byte[] key) { public KeyPool rotation(byte[]... keys) { List list = new ArrayList<>(keys.length); for (int i = 0; i < keys.length; i++) { - list.add(new DynamicKey(i, keys[i])); + list.add(new DynamicKey(Integer.toString(i), keys[i])); } return new RotationPool(list); } @@ -68,7 +68,7 @@ public FixedPool(byte[] key) { } public FixedPool(long id, byte[] key) { - this.key = new DynamicKey(id, key); + this.key = new DynamicKey(Long.toString(id), key); } @Override diff --git a/tile-crypto/src/main/java/com/power4j/tile/crypto/dynamic/SimpleDynamicDecrypt.java b/tile-crypto/src/main/java/com/power4j/tile/crypto/dynamic/SimpleDynamicDecrypt.java index f6778bd..8940371 100644 --- a/tile-crypto/src/main/java/com/power4j/tile/crypto/dynamic/SimpleDynamicDecrypt.java +++ b/tile-crypto/src/main/java/com/power4j/tile/crypto/dynamic/SimpleDynamicDecrypt.java @@ -28,6 +28,7 @@ import java.util.Arrays; import java.util.List; import java.util.function.Function; +import java.util.function.Supplier; /** * @author CJ (power4j@outlook.com) @@ -49,9 +50,11 @@ public class SimpleDynamicDecrypt implements DynamicDecrypt { private final Function checksumCalculator; + private final Supplier paramterSupplier; + @Override public DynamicDecryptResult decrypt(CipherBlob store) { - final long timestamp = System.currentTimeMillis(); + final long timestamp = paramterSupplier.get(); List keyList = keyPool.decryptKeys(timestamp); List ivList = ivPool.decryptKeys(timestamp); if (keyList.isEmpty()) { @@ -94,7 +97,7 @@ protected DecryptInfo tryOne(CipherBlob store, DynamicKey key, @Nullable Dynamic Verified verified = cipher.decrypt(store, false); return DecryptInfo.builder() .matched(verified.isPass()) - .keyIndex(key.getIndex()) + .keyTag(key.getTag()) .checksum(store.getChecksum()) .data(verified.getData()) .build(); diff --git a/tile-crypto/src/main/java/com/power4j/tile/crypto/dynamic/TimeBasedPool.java b/tile-crypto/src/main/java/com/power4j/tile/crypto/dynamic/TimeBasedPool.java new file mode 100644 index 0000000..700651a --- /dev/null +++ b/tile-crypto/src/main/java/com/power4j/tile/crypto/dynamic/TimeBasedPool.java @@ -0,0 +1,149 @@ +/* + * Copyright 2019-2024 the original author or authors. + * + * 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 + * + * https://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 com.power4j.tile.crypto.dynamic; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.time.Duration; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Optional; + +/** + * @author CJ (power4j@outlook.com) + * @since 1.6 + */ +public class TimeBasedPool implements KeyPool { + + private final KeyGenerator generator; + + private final int windowSize; + + private final long intervalMills; + + private final int keySize; + + public static Builder ofSize(int keySize) { + return new Builder(keySize); + } + + TimeBasedPool(KeyGenerator generator, int keySize, int windowSize, long intervalMills) { + if (windowSize <= 0) { + throw new IllegalArgumentException("window size must > 0"); + } + if (intervalMills <= 0) { + throw new IllegalArgumentException("interval must > 0"); + } + this.generator = generator; + this.keySize = keySize; + this.windowSize = windowSize; + this.intervalMills = intervalMills; + } + + @Override + public Optional encryptKey(long param) { + return Optional.of(atOffset(param, 0)); + } + + @Override + public List decryptKeys(long param) { + List keys = new ArrayList<>(windowSize * 2 + 1); + keys.add(atOffset(param, 0)); + for (int i = 1; i <= windowSize; i++) { + keys.add(atOffset(param, i)); + keys.add(atOffset(param, -i)); + } + return keys; + } + + protected DynamicKey atOffset(long timestamp, int offset) { + long seed = (timestamp + (long) offset * intervalMills) / intervalMills; + return new DynamicKey(timestamp + ":" + offset, generator.generate(seed, keySize)); + } + + static class KeyGenerator { + + private final byte[] padding; + + KeyGenerator(byte[] padding) { + if (padding.length == 0) { + throw new IllegalArgumentException("padding must not be null or empty"); + } + this.padding = Arrays.copyOf(padding, padding.length); + } + + public byte[] generate(long timestamp, int keySize) { + byte[] base = longBe(timestamp); + int fillAt = base.length; + byte[] key = Arrays.copyOf(base, keySize); + while (fillAt < keySize) { + key[fillAt] = padding[fillAt % padding.length]; + fillAt++; + } + return key; + } + + static byte[] longBe(long value) { + ByteBuffer buffer = ByteBuffer.allocate(Long.BYTES).order(ByteOrder.BIG_ENDIAN); + buffer.putLong(0, value); + return buffer.array(); + } + + } + + public static class Builder { + + private final int keySize; + + private int windowSize; + + private long interval; + + private byte[] fillBytes; + + Builder(int keySize) { + this.keySize = keySize; + } + + public Builder windowSize(int windowSize) { + this.windowSize = windowSize; + return this; + } + + public Builder intervalSeconds(long seconds) { + this.interval = seconds * 1000L; + return this; + } + + public Builder interval(Duration interval) { + this.interval = interval.toMillis(); + return this; + } + + public Builder fillBytes(byte[] fillBytes) { + this.fillBytes = fillBytes; + return this; + } + + public TimeBasedPool build() { + return new TimeBasedPool(new KeyGenerator(fillBytes), keySize, windowSize, interval); + } + + } + +} diff --git a/tile-crypto/src/test/java/com/power4j/tile/crypto/dynamic/SimpleDynamicDecryptTest.java b/tile-crypto/src/test/java/com/power4j/tile/crypto/dynamic/SimpleDynamicDecryptTest.java index 5b823fe..f935e9a 100644 --- a/tile-crypto/src/test/java/com/power4j/tile/crypto/dynamic/SimpleDynamicDecryptTest.java +++ b/tile-crypto/src/test/java/com/power4j/tile/crypto/dynamic/SimpleDynamicDecryptTest.java @@ -49,7 +49,7 @@ class SimpleDynamicDecryptTest { private final Function checksumCalculator = (b) -> Arrays.copyOf(b, 8); @Test - void decrypt() { + void rotationDecryptTest() { byte[] plain = "hello".getBytes(StandardCharsets.UTF_8); KeyPool pool = Pools.rotation(testKey1, testKey2, testKey3); SimpleDynamicDecrypt dec = DynamicDecryptBuilder.sm4Cbc() @@ -72,4 +72,42 @@ void decrypt() { Assertions.assertArrayEquals(plain, result.requiredMatched().getData()); } + @Test + void timeBasedDecryptTest() { + byte[] plain = "hello".getBytes(StandardCharsets.UTF_8); + long time = System.currentTimeMillis(); + int windowSize = 3; + int intervalSeconds = 60; + KeyPool pool = TimeBasedPool.ofSize(16) + .fillBytes(new byte[] { 0x01, 0x02, 0x03, 0x04 }) + .windowSize(windowSize) + .intervalSeconds(intervalSeconds) + .build(); + + DynamicDecryptBuilder builder = DynamicDecryptBuilder.sm4Cbc() + .checksumCalculator(checksumCalculator) + .keyPool(pool) + .ivPool(Pools.fixed(testIv)); + + BlockCipher enc = Sm4Util.builder(Spec.MODE_CBC, Spec.PADDING_PKCS7) + .secretKey(testKey3) + .ivParameter(testIv) + .checksumCalculator(checksumCalculator) + .build(); + CipherBlobEnvelope envelope = enc.encryptEnvelope(plain); + CipherBlob store = new CipherBlob(envelope.getCipher(), envelope.getChecksum()); + + DynamicDecryptResult result = builder.parameterSupplier(() -> time).simple().decrypt(store); + Assertions.assertTrue(result.success()); + Assertions.assertArrayEquals(plain, result.requiredMatched().getData()); + + result = builder.parameterSupplier(() -> time + intervalSeconds * 1000 * windowSize).simple().decrypt(store); + Assertions.assertTrue(result.success()); + Assertions.assertArrayEquals(plain, result.requiredMatched().getData()); + + result = builder.parameterSupplier(() -> time - intervalSeconds * 1000 * windowSize).simple().decrypt(store); + Assertions.assertTrue(result.success()); + Assertions.assertArrayEquals(plain, result.requiredMatched().getData()); + } + } diff --git a/tile-crypto/src/test/java/com/power4j/tile/crypto/dynamic/TimeBasedPoolTest.java b/tile-crypto/src/test/java/com/power4j/tile/crypto/dynamic/TimeBasedPoolTest.java new file mode 100644 index 0000000..158f543 --- /dev/null +++ b/tile-crypto/src/test/java/com/power4j/tile/crypto/dynamic/TimeBasedPoolTest.java @@ -0,0 +1,83 @@ +/* + * Copyright 2019-2024 the original author or authors. + * + * 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 + * + * https://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 com.power4j.tile.crypto.dynamic; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.time.Duration; +import java.util.List; + +/** + * @author CJ (power4j@outlook.com) + * @since 1.0 + */ +class TimeBasedPoolTest { + + @Test + void keyGenerateTest() { + byte[] fills = new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; + + Assertions.assertArrayEquals(new byte[] { 1 }, + new TimeBasedPool.KeyGenerator(fills).generate(0x0102030405060708L, 1)); + + Assertions.assertArrayEquals(new byte[] { 1, 2 }, + new TimeBasedPool.KeyGenerator(fills).generate(0x0102030405060708L, 2)); + + Assertions.assertArrayEquals(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8 }, + new TimeBasedPool.KeyGenerator(fills).generate(0x0102030405060708L, 8)); + + Assertions.assertArrayEquals(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 8 }, + new TimeBasedPool.KeyGenerator(fills).generate(0x0102030405060708L, 9)); + + Assertions.assertArrayEquals(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 8, 9 }, + new TimeBasedPool.KeyGenerator(fills).generate(0x0102030405060708L, 10)); + + Assertions.assertArrayEquals(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 8, 9, 0 }, + new TimeBasedPool.KeyGenerator(fills).generate(0x0102030405060708L, 11)); + } + + @Test + void shouldGenerateSameKey() { + TimeBasedPool.KeyGenerator generator = new TimeBasedPool.KeyGenerator(new byte[] { 1 }); + long input = 1L; + byte[] last = null; + for (int i = 0; i < 100; i++) { + byte[] key = generator.generate(input, 32); + if (last != null) { + Assertions.assertArrayEquals(last, key); + } + last = key; + } + } + + @Test + void keyPoolGeneratorTest() { + long input = 1L; + TimeBasedPool pool = TimeBasedPool.ofSize(16) + .windowSize(5) + .interval(Duration.ofMinutes(1)) + .fillBytes(new byte[] { 1 }) + .build(); + final List decryptKeys = pool.decryptKeys(input); + Assertions.assertEquals(11, decryptKeys.size()); + final DynamicKey encryptKey = pool.encryptKey(input).orElse(null); + Assertions.assertNotNull(encryptKey); + Assertions.assertArrayEquals(encryptKey.getKey(), decryptKeys.get(0).getKey()); + } + +} From 7162e9245a14ef30312704c36eae239d3d04e808 Mon Sep 17 00:00:00 2001 From: cj Date: Sun, 15 Sep 2024 03:16:47 +0800 Subject: [PATCH 5/5] refactor: Polish crypto api --- ...pher.java => BouncyCastleQuickCipher.java} | 48 +++++---- ...obEnvelope.java => CipherBlobDetails.java} | 2 +- ...xtEnvelope.java => CiphertextDetails.java} | 4 +- .../{CipherBlob.java => QuickCipher.java} | 11 +- ...erBuilder.java => QuickCipherBuilder.java} | 48 ++++----- .../core/{BlockCipher.java => QuickDec.java} | 66 ++---------- .../power4j/tile/crypto/core/QuickEnc.java | 79 ++++++++++++++ .../com/power4j/tile/crypto/core/Slice.java | 101 ++++++++++++++++++ .../power4j/tile/crypto/core/TextCipher.java | 2 +- .../tile/crypto/core/TextCipherBuilder.java | 40 +++---- .../core/{TextDecrypt.java => TextDec.java} | 2 +- .../core/{TextEncrypt.java => TextEnc.java} | 4 +- .../tile/crypto/core/UncheckedCipher.java | 46 ++++++++ .../tile/crypto/dynamic/DecryptInfo.java | 5 +- .../tile/crypto/dynamic/DynamicDecrypt.java | 4 +- .../power4j/tile/crypto/dynamic/KeyPool.java | 8 +- .../power4j/tile/crypto/dynamic/Pools.java | 22 ++-- .../crypto/dynamic/SimpleDynamicDecrypt.java | 26 ++--- .../tile/crypto/dynamic/TimeBasedPool.java | 7 +- .../power4j/tile/crypto/utils/CryptoUtil.java | 21 ++-- .../power4j/tile/crypto/utils/Sm4Util.java | 26 ++--- ....java => BouncyCastleQuickCipherTest.java} | 30 +++--- .../power4j/tile/crypto/core/SliceTest.java | 90 ++++++++++++++++ .../tile/crypto/core/Sm4TextCipherTest.java | 2 +- .../dynamic/SimpleDynamicDecryptTest.java | 28 ++--- .../crypto/dynamic/TimeBasedPoolTest.java | 4 +- .../tile/crypto/utils/CryptoUtilTest.java | 2 - .../tile/crypto/utils/Sm4UtilTest.java | 24 ++--- 28 files changed, 510 insertions(+), 242 deletions(-) rename tile-crypto/src/main/java/com/power4j/tile/crypto/bc/{BouncyCastleBlockCipher.java => BouncyCastleQuickCipher.java} (62%) rename tile-crypto/src/main/java/com/power4j/tile/crypto/core/{CipherBlobEnvelope.java => CipherBlobDetails.java} (97%) rename tile-crypto/src/main/java/com/power4j/tile/crypto/core/{CiphertextEnvelope.java => CiphertextDetails.java} (91%) rename tile-crypto/src/main/java/com/power4j/tile/crypto/core/{CipherBlob.java => QuickCipher.java} (79%) rename tile-crypto/src/main/java/com/power4j/tile/crypto/core/{BlockCipherBuilder.java => QuickCipherBuilder.java} (74%) rename tile-crypto/src/main/java/com/power4j/tile/crypto/core/{BlockCipher.java => QuickDec.java} (57%) create mode 100644 tile-crypto/src/main/java/com/power4j/tile/crypto/core/QuickEnc.java create mode 100644 tile-crypto/src/main/java/com/power4j/tile/crypto/core/Slice.java rename tile-crypto/src/main/java/com/power4j/tile/crypto/core/{TextDecrypt.java => TextDec.java} (96%) rename tile-crypto/src/main/java/com/power4j/tile/crypto/core/{TextEncrypt.java => TextEnc.java} (90%) create mode 100644 tile-crypto/src/main/java/com/power4j/tile/crypto/core/UncheckedCipher.java rename tile-crypto/src/test/java/com/power4j/tile/crypto/bc/{BouncyCastleBlockCipherTest.java => BouncyCastleQuickCipherTest.java} (70%) create mode 100644 tile-crypto/src/test/java/com/power4j/tile/crypto/core/SliceTest.java diff --git a/tile-crypto/src/main/java/com/power4j/tile/crypto/bc/BouncyCastleBlockCipher.java b/tile-crypto/src/main/java/com/power4j/tile/crypto/bc/BouncyCastleQuickCipher.java similarity index 62% rename from tile-crypto/src/main/java/com/power4j/tile/crypto/bc/BouncyCastleBlockCipher.java rename to tile-crypto/src/main/java/com/power4j/tile/crypto/bc/BouncyCastleQuickCipher.java index 1e71c5c..e6b03c1 100644 --- a/tile-crypto/src/main/java/com/power4j/tile/crypto/bc/BouncyCastleBlockCipher.java +++ b/tile-crypto/src/main/java/com/power4j/tile/crypto/bc/BouncyCastleQuickCipher.java @@ -16,12 +16,14 @@ package com.power4j.tile.crypto.bc; -import com.power4j.tile.crypto.core.BlockCipher; -import com.power4j.tile.crypto.core.CipherBlob; -import com.power4j.tile.crypto.core.CipherBlobEnvelope; +import com.power4j.tile.crypto.core.CipherBlobDetails; import com.power4j.tile.crypto.core.GeneralCryptoException; +import com.power4j.tile.crypto.core.QuickCipher; +import com.power4j.tile.crypto.core.Slice; +import com.power4j.tile.crypto.core.UncheckedCipher; import com.power4j.tile.crypto.core.Verified; import com.power4j.tile.crypto.utils.CryptoUtil; +import org.springframework.lang.Nullable; import javax.crypto.Cipher; import javax.crypto.spec.IvParameterSpec; @@ -35,9 +37,9 @@ * @author CJ (power4j@outlook.com) * @since 1.6 */ -public class BouncyCastleBlockCipher implements BlockCipher { +public class BouncyCastleQuickCipher implements QuickCipher { - private final String transformation; + private final Cipher cipher; private final Supplier keySupplier; @@ -47,12 +49,13 @@ public class BouncyCastleBlockCipher implements BlockCipher { private final Function checksumCalculator; - private final BiFunction checksumVerifier; + private final BiFunction checksumVerifier; - public BouncyCastleBlockCipher(String transformation, Supplier keySupplier, + public BouncyCastleQuickCipher(String transformation, Supplier keySupplier, Supplier ivParameterSpecSupplier, Function checksumCalculator, - BiFunction checksumVerifier) { - this.transformation = transformation; + BiFunction checksumVerifier) { + this.cipher = CryptoUtil.createCipher(transformation); + ; this.keySupplier = keySupplier; this.ivParameterSpecSupplier = ivParameterSpecSupplier; this.checksumCalculator = checksumCalculator; @@ -61,21 +64,19 @@ public BouncyCastleBlockCipher(String transformation, Supplier ke } @Override - public CipherBlobEnvelope encryptEnvelope(byte[] data) throws GeneralCryptoException { + public CipherBlobDetails encrypt(byte[] data, int offset, int length) throws GeneralCryptoException { final IvParameterSpec ivParameter = ivParameterSpecSupplier.get(); byte[] encrypted; byte[] checksum; try { checksum = checksumCalculator.apply(data); - Cipher cipher = CryptoUtil.createCipher(transformation); - cipher.init(Cipher.ENCRYPT_MODE, keySupplier.get(), ivParameter); - encrypted = cipher.doFinal(data); + encrypted = oneStep(Cipher.ENCRYPT_MODE, keySupplier.get(), ivParameter, Slice.wrap(data)); } - catch (GeneralSecurityException e) { - throw new GeneralCryptoException(e.getMessage(), e); + catch (Exception e) { + throw CryptoUtil.wrapGeneralCryptoException(null, e); } byte[] ivBytes = ivParameter == null ? null : ivParameter.getIV(); - return CipherBlobEnvelope.builder() + return CipherBlobDetails.builder() .algorithm(transformationParts[0]) .mode(transformationParts[1]) .padding(transformationParts[2]) @@ -86,12 +87,11 @@ public CipherBlobEnvelope encryptEnvelope(byte[] data) throws GeneralCryptoExcep } @Override - public Verified decrypt(CipherBlob store, boolean skipCheck) throws GeneralCryptoException { + public Verified decrypt(UncheckedCipher input, boolean skipCheck) throws GeneralCryptoException { byte[] decrypted; try { - Cipher cipher = CryptoUtil.createCipher(transformation); - cipher.init(Cipher.DECRYPT_MODE, keySupplier.get(), ivParameterSpecSupplier.get()); - decrypted = cipher.doFinal(store.getCipher()); + decrypted = oneStep(Cipher.DECRYPT_MODE, keySupplier.get(), ivParameterSpecSupplier.get(), + input.getCipher()); } catch (GeneralSecurityException e) { return Verified.fail(null, e); @@ -99,8 +99,14 @@ public Verified decrypt(CipherBlob store, boolean skipCheck) throws Gene if (skipCheck) { return Verified.pass(decrypted); } - return checksumVerifier.apply(store, decrypted) ? Verified.pass(decrypted) : Verified.fail(decrypted, null); + return checksumVerifier.apply(input, decrypted) ? Verified.pass(decrypted) : Verified.fail(decrypted, null); + + } + protected final synchronized byte[] oneStep(int mode, SecretKeySpec key, @Nullable IvParameterSpec iv, Slice data) + throws GeneralSecurityException { + cipher.init(mode, key, iv); + return cipher.doFinal(data.getData(), data.getOffset(), data.getLength()); } } diff --git a/tile-crypto/src/main/java/com/power4j/tile/crypto/core/CipherBlobEnvelope.java b/tile-crypto/src/main/java/com/power4j/tile/crypto/core/CipherBlobDetails.java similarity index 97% rename from tile-crypto/src/main/java/com/power4j/tile/crypto/core/CipherBlobEnvelope.java rename to tile-crypto/src/main/java/com/power4j/tile/crypto/core/CipherBlobDetails.java index eae5151..78ca4c5 100644 --- a/tile-crypto/src/main/java/com/power4j/tile/crypto/core/CipherBlobEnvelope.java +++ b/tile-crypto/src/main/java/com/power4j/tile/crypto/core/CipherBlobDetails.java @@ -28,7 +28,7 @@ */ @Getter @Builder -public class CipherBlobEnvelope { +public class CipherBlobDetails { private final String algorithm; diff --git a/tile-crypto/src/main/java/com/power4j/tile/crypto/core/CiphertextEnvelope.java b/tile-crypto/src/main/java/com/power4j/tile/crypto/core/CiphertextDetails.java similarity index 91% rename from tile-crypto/src/main/java/com/power4j/tile/crypto/core/CiphertextEnvelope.java rename to tile-crypto/src/main/java/com/power4j/tile/crypto/core/CiphertextDetails.java index c66ec33..8c8f76b 100644 --- a/tile-crypto/src/main/java/com/power4j/tile/crypto/core/CiphertextEnvelope.java +++ b/tile-crypto/src/main/java/com/power4j/tile/crypto/core/CiphertextDetails.java @@ -28,7 +28,7 @@ */ @Getter @Builder -public class CiphertextEnvelope { +public class CiphertextDetails { private final String encoding; @@ -57,7 +57,7 @@ public Optional getIvOptional(boolean trimToNull) { @Override public String toString() { - return "CiphertextEnvelope{" + "encoding='" + encoding + '\'' + ", algorithm='" + algorithm + '\'' + return "CiphertextDetails{" + "encoding='" + encoding + '\'' + ", algorithm='" + algorithm + '\'' + ", padding='" + padding + '\'' + ", mode='" + mode + '\'' + ", iv='" + iv + '\'' + ", ciphertext='" + ciphertext + '\'' + ", checksum='" + checksum + '\'' + '}'; } diff --git a/tile-crypto/src/main/java/com/power4j/tile/crypto/core/CipherBlob.java b/tile-crypto/src/main/java/com/power4j/tile/crypto/core/QuickCipher.java similarity index 79% rename from tile-crypto/src/main/java/com/power4j/tile/crypto/core/CipherBlob.java rename to tile-crypto/src/main/java/com/power4j/tile/crypto/core/QuickCipher.java index 3a78b73..ef91b19 100644 --- a/tile-crypto/src/main/java/com/power4j/tile/crypto/core/CipherBlob.java +++ b/tile-crypto/src/main/java/com/power4j/tile/crypto/core/QuickCipher.java @@ -16,19 +16,10 @@ package com.power4j.tile.crypto.core; -import lombok.Getter; -import lombok.RequiredArgsConstructor; - /** * @author CJ (power4j@outlook.com) * @since 1.0 */ -@Getter -@RequiredArgsConstructor -public class CipherBlob { - - private final byte[] cipher; - - private final byte[] checksum; +public interface QuickCipher extends QuickEnc, QuickDec { } diff --git a/tile-crypto/src/main/java/com/power4j/tile/crypto/core/BlockCipherBuilder.java b/tile-crypto/src/main/java/com/power4j/tile/crypto/core/QuickCipherBuilder.java similarity index 74% rename from tile-crypto/src/main/java/com/power4j/tile/crypto/core/BlockCipherBuilder.java rename to tile-crypto/src/main/java/com/power4j/tile/crypto/core/QuickCipherBuilder.java index 6833d44..8714f95 100644 --- a/tile-crypto/src/main/java/com/power4j/tile/crypto/core/BlockCipherBuilder.java +++ b/tile-crypto/src/main/java/com/power4j/tile/crypto/core/QuickCipherBuilder.java @@ -16,7 +16,7 @@ package com.power4j.tile.crypto.core; -import com.power4j.tile.crypto.bc.BouncyCastleBlockCipher; +import com.power4j.tile.crypto.bc.BouncyCastleQuickCipher; import com.power4j.tile.crypto.bc.Spec; import com.power4j.tile.crypto.utils.CryptoUtil; import com.power4j.tile.crypto.utils.Validate; @@ -29,7 +29,7 @@ import java.util.function.Supplier; /** - * {@link BlockCipher} Builder
+ * {@link QuickCipher} Builder
*
    *
  • algorithmName: 密码算法的名称,如 AES,SM4
  • *
  • mode: 加密模式,如 ECB, CBC
  • @@ -45,7 +45,7 @@ * @see Spec * @see TextCipherBuilder */ -public class BlockCipherBuilder { +public class QuickCipherBuilder { private final String algorithmName; @@ -59,90 +59,90 @@ public class BlockCipherBuilder { private Function checksumCalculator; - private BiFunction checksumVerifier; + private BiFunction checksumVerifier; - BlockCipherBuilder(String algorithmName) { + QuickCipherBuilder(String algorithmName) { this.algorithmName = algorithmName; }; - public static BlockCipherBuilder algorithm(String algorithmName) { - return new BlockCipherBuilder(algorithmName); + public static QuickCipherBuilder algorithm(String algorithmName) { + return new QuickCipherBuilder(algorithmName); } - public BlockCipherBuilder mode(String mode) { + public QuickCipherBuilder mode(String mode) { this.mode = mode; return this; } - public BlockCipherBuilder padding(String padding) { + public QuickCipherBuilder padding(String padding) { this.padding = padding; return this; } - public BlockCipherBuilder secretKeySpecSupplier(Supplier supplier) { + public QuickCipherBuilder secretKeySpecSupplier(Supplier supplier) { this.secretKeySpecSupplier = supplier; return this; } - public BlockCipherBuilder secretKey(byte[] key) { + public QuickCipherBuilder secretKey(byte[] key) { return secretKeySpecSupplier(() -> CryptoUtil.createKey(key, algorithmName)); } - public BlockCipherBuilder secretKeyHex(String val) { + public QuickCipherBuilder secretKeyHex(String val) { this.secretKeySpecSupplier = () -> CryptoUtil.createKey(CryptoUtil.decodeHex(val, null), algorithmName); return this; } - public BlockCipherBuilder secretKeyBase64(String val) { + public QuickCipherBuilder secretKeyBase64(String val) { this.secretKeySpecSupplier = () -> CryptoUtil.createKey(CryptoUtil.decodeBase64(val, null), algorithmName); return this; } - public BlockCipherBuilder ivParameterSpecSupplier(Supplier supplier) { + public QuickCipherBuilder ivParameterSpecSupplier(Supplier supplier) { this.ivParameterSpecSupplier = supplier; return this; } - public BlockCipherBuilder ivParameter(@Nullable byte[] iv) { + public QuickCipherBuilder ivParameter(@Nullable byte[] iv) { if (iv == null) { return ivParameterSpecSupplier(() -> null); } return ivParameterSpecSupplier(() -> new IvParameterSpec(iv)); } - public BlockCipherBuilder ivParameterHex(@Nullable String val) { + public QuickCipherBuilder ivParameterHex(@Nullable String val) { if (val == null) { return ivParameterSpecSupplier(() -> null); } return ivParameterSpecSupplier(() -> new IvParameterSpec(CryptoUtil.decodeHex(val, null))); } - public BlockCipherBuilder ivParameterBase64(String val) { + public QuickCipherBuilder ivParameterBase64(String val) { this.ivParameterSpecSupplier = () -> new IvParameterSpec(CryptoUtil.decodeBase64(val, null)); return this; } - public BlockCipherBuilder checksumCalculator(@Nullable Function calculator) { + public QuickCipherBuilder checksumCalculator(@Nullable Function calculator) { this.checksumCalculator = calculator; return this; } - public BlockCipherBuilder sm3ChecksumCalculator() { + public QuickCipherBuilder sm3ChecksumCalculator() { this.checksumCalculator = CryptoUtil.SM3_CHECKSUM_CALCULATOR; return this; } - public BlockCipherBuilder checksumVerifier(BiFunction verifier) { + public QuickCipherBuilder checksumVerifier(BiFunction verifier) { this.checksumVerifier = verifier; return this; } - public BlockCipherBuilder sm3ChecksumVerifier() { + public QuickCipherBuilder sm3ChecksumVerifier() { this.checksumVerifier = CryptoUtil.SM3_CHECKSUM_VERIFIER; return this; } - public BouncyCastleBlockCipher build() { + public BouncyCastleQuickCipher build() { Validate.notEmpty(algorithmName, "algorithmName must not be empty"); Validate.notEmpty(mode, "mode must not be empty"); Validate.notEmpty(padding, "padding must not be empty"); @@ -150,12 +150,12 @@ public BouncyCastleBlockCipher build() { Function calculator = checksumCalculator == null ? CryptoUtil.EMPTY_CHECKSUM_CALCULATOR : checksumCalculator; - BiFunction verifier = checksumVerifier == null + BiFunction verifier = checksumVerifier == null ? CryptoUtil.IGNORED_CHECKSUM_VERIFIER : checksumVerifier; Supplier ivSpecSupplier = ivParameterSpecSupplier == null ? () -> null : ivParameterSpecSupplier; String transformation = CryptoUtil.transformation(algorithmName, mode, padding); - return new BouncyCastleBlockCipher(transformation, secretKeySpecSupplier, ivSpecSupplier, calculator, verifier); + return new BouncyCastleQuickCipher(transformation, secretKeySpecSupplier, ivSpecSupplier, calculator, verifier); } } diff --git a/tile-crypto/src/main/java/com/power4j/tile/crypto/core/BlockCipher.java b/tile-crypto/src/main/java/com/power4j/tile/crypto/core/QuickDec.java similarity index 57% rename from tile-crypto/src/main/java/com/power4j/tile/crypto/core/BlockCipher.java rename to tile-crypto/src/main/java/com/power4j/tile/crypto/core/QuickDec.java index 3cb37ba..7e8340a 100644 --- a/tile-crypto/src/main/java/com/power4j/tile/crypto/core/BlockCipher.java +++ b/tile-crypto/src/main/java/com/power4j/tile/crypto/core/QuickDec.java @@ -17,32 +17,17 @@ package com.power4j.tile.crypto.core; import com.power4j.tile.crypto.core.encode.HexEncoder; +import com.power4j.tile.crypto.utils.CryptoUtil; import com.power4j.tile.crypto.wrapper.InputDecoder; import com.power4j.tile.crypto.wrapper.OutputEncoder; /** + * 快速解密操作,适用于少量数据的解密 + * * @author CJ (power4j@outlook.com) - * @since 1.0 + * @since 1.6 */ -public interface BlockCipher { - - /** - * 加密 - * @param data 输入数据 - * @return 返回密文 - * @throws GeneralCryptoException - */ - default byte[] encrypt(byte[] data) throws GeneralCryptoException { - return encryptEnvelope(data).getCipher(); - } - - /** - * 加密 - * @param data 输入数据 - * @return CipherEnvelope - * @throws GeneralCryptoException - */ - CipherBlobEnvelope encryptEnvelope(byte[] data) throws GeneralCryptoException; +public interface QuickDec { /** * 解密 @@ -51,16 +36,11 @@ default byte[] encrypt(byte[] data) throws GeneralCryptoException { * @throws GeneralCryptoException */ default byte[] decrypt(byte[] data) throws GeneralCryptoException { - Verified result = decrypt(new CipherBlob(data, null), true); + Verified result = decrypt(UncheckedCipher.of(data), true); if (!result.isPass()) { Throwable throwable = result.getCause(); if (throwable != null) { - if (throwable instanceof GeneralCryptoException) { - throw (GeneralCryptoException) throwable; - } - else { - throw new GeneralCryptoException(throwable); - } + throw CryptoUtil.wrapGeneralCryptoException("Data verification failed", throwable); } throw new GeneralCryptoException("Data verification failed"); } @@ -70,28 +50,12 @@ default byte[] decrypt(byte[] data) throws GeneralCryptoException { /** * 解密并验证校验和 - * @param store 密文信息 + * @param input 密文信息 * @param skipCheck 是否跳过校验 * @return 返回解密数据 * @throws GeneralCryptoException */ - Verified decrypt(CipherBlob store, boolean skipCheck) throws GeneralCryptoException; - - /** - * 加密,支持输入和输出的转换 - * @param decoder 输入解码器 - * @param encoder 输出编码器 - * @param input 输入对象 - * @return 返回编码后的输出 - * @param 输入类型 - * @param 输出类型 - * @throws GeneralCryptoException - */ - default R encryptWith(InputDecoder decoder, OutputEncoder encoder, T input) - throws GeneralCryptoException { - byte[] data = decoder.decode(input); - return encoder.encode(encrypt(data)); - } + Verified decrypt(UncheckedCipher input, boolean skipCheck) throws GeneralCryptoException; /** * 解密,支持输入和输出的转换 @@ -109,18 +73,6 @@ default R decryptWith(InputDecoder decoder, OutputEncoder encoder, return encoder.encode(decrypt(data)); } - /** - * 数据加密,输出16进制字符串 - * @param data 需要加密的数据 - * @return 加密后的16进制字符串 - * @throws GeneralCryptoException - * @deprecated use {@link TextCipher } instead - * @see TextCipherBuilder - */ - default String encryptHex(byte[] data) throws GeneralCryptoException { - return encryptWith(InputDecoder.NO_OP, HexEncoder.DEFAULT::encode, data); - } - /** * 数据解密,输入16进制字符串 * @param data 需要解密的数据,16进制字符串格式 diff --git a/tile-crypto/src/main/java/com/power4j/tile/crypto/core/QuickEnc.java b/tile-crypto/src/main/java/com/power4j/tile/crypto/core/QuickEnc.java new file mode 100644 index 0000000..26f8083 --- /dev/null +++ b/tile-crypto/src/main/java/com/power4j/tile/crypto/core/QuickEnc.java @@ -0,0 +1,79 @@ +/* + * Copyright 2019-2024 the original author or authors. + * + * 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 + * + * https://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 com.power4j.tile.crypto.core; + +import com.power4j.tile.crypto.core.encode.HexEncoder; +import com.power4j.tile.crypto.wrapper.InputDecoder; +import com.power4j.tile.crypto.wrapper.OutputEncoder; + +/** + * 快速加密密操作,适用于少量数据的加密 + * + * @author CJ (power4j@outlook.com) + * @since 1.6 + */ +public interface QuickEnc { + + /** + * 加密 + * @param data 输入数据 + * @param offset 开始位置 + * @param length 长度 + * @return CipherEnvelope + * @throws GeneralCryptoException + */ + CipherBlobDetails encrypt(byte[] data, int offset, int length) throws GeneralCryptoException; + + /** + * 加密 + * @param data 输入数据 + * @return CipherEnvelope + * @throws GeneralCryptoException + */ + default CipherBlobDetails encrypt(byte[] data) throws GeneralCryptoException { + return encrypt(data, 0, data.length); + } + + /** + * 加密,支持输入和输出的转换 + * @param decoder 输入解码器 + * @param encoder 输出编码器 + * @param input 输入对象 + * @return 返回编码后的输出 + * @param 输入类型 + * @param 输出类型 + * @throws GeneralCryptoException + */ + default R encryptWith(InputDecoder decoder, OutputEncoder encoder, T input) + throws GeneralCryptoException { + byte[] data = decoder.decode(input); + return encoder.encode(encrypt(data).getCipher()); + } + + /** + * 数据加密,输出16进制字符串 + * @param data 需要加密的数据 + * @return 加密后的16进制字符串 + * @throws GeneralCryptoException + * @deprecated use {@link TextCipher } instead + * @see TextCipherBuilder + */ + default String encryptHex(byte[] data) throws GeneralCryptoException { + return encryptWith(InputDecoder.NO_OP, HexEncoder.DEFAULT::encode, data); + } + +} diff --git a/tile-crypto/src/main/java/com/power4j/tile/crypto/core/Slice.java b/tile-crypto/src/main/java/com/power4j/tile/crypto/core/Slice.java new file mode 100644 index 0000000..c0c2f2c --- /dev/null +++ b/tile-crypto/src/main/java/com/power4j/tile/crypto/core/Slice.java @@ -0,0 +1,101 @@ +/* + * Copyright 2019-2024 the original author or authors. + * + * 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 + * + * https://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 com.power4j.tile.crypto.core; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.springframework.lang.Nullable; + +import java.util.Arrays; + +/** + * @author CJ (power4j@outlook.com) + * @since 1.0 + */ +@Getter +@RequiredArgsConstructor +public class Slice { + + private final byte[] data; + + private final int offset; + + private final int length; + + public static Slice wrap(@Nullable byte[] data) { + if (data == null) { + return range(new byte[0], 0, 0); + } + return range(data, 0, data.length); + } + + public static Slice range(byte[] data, int offset, int length) { + if (offset < 0 || length < 0 || offset + length > data.length) { + throw new IllegalArgumentException("Illegal offset or length"); + } + return new Slice(data, offset, length); + } + + public static Slice remaining(byte[] data, int offset) { + return range(data, offset, data.length - offset); + } + + public static Slice copyOf(byte[] data) { + return copyOfRange(data, 0, data.length); + } + + public static Slice copyOfRange(byte[] data, int offset, int length) { + return wrap(Arrays.copyOfRange(data, offset, offset + length)); + } + + public static Slice copyOfRemaining(byte[] data, int offset) { + return copyOfRange(data, offset, data.length - offset); + } + + public boolean dataEquals(Slice other) { + return dataEquals(other.data, other.offset, other.length); + } + + public byte[] unwrap() { + return Arrays.copyOfRange(data, offset, offset + length); + } + + public Slice sub(int offset, int length) { + if (offset < 0 || length < 0 || offset + length > this.length) { + throw new IllegalArgumentException("Illegal offset or length"); + } + return new Slice(this.data, this.offset + offset, length); + } + + public boolean dataEquals(byte[] other) { + return dataEquals(other, 0, other.length); + } + + public boolean dataEquals(byte[] other, int otherOffset, int otherLength) { + if (length != otherLength) { + return false; + } + for (int i = 0; i < length; i++) { + if (data[offset + i] != other[otherOffset + i]) { + return false; + } + } + return true; + + } + +} diff --git a/tile-crypto/src/main/java/com/power4j/tile/crypto/core/TextCipher.java b/tile-crypto/src/main/java/com/power4j/tile/crypto/core/TextCipher.java index 6ed1ecb..7771fd5 100644 --- a/tile-crypto/src/main/java/com/power4j/tile/crypto/core/TextCipher.java +++ b/tile-crypto/src/main/java/com/power4j/tile/crypto/core/TextCipher.java @@ -22,6 +22,6 @@ * @author CJ (power4j@outlook.com) * @since 1.6 */ -public interface TextCipher extends TextDecrypt, TextEncrypt { +public interface TextCipher extends TextDec, TextEnc { } diff --git a/tile-crypto/src/main/java/com/power4j/tile/crypto/core/TextCipherBuilder.java b/tile-crypto/src/main/java/com/power4j/tile/crypto/core/TextCipherBuilder.java index 6e0d1ae..d4078cc 100644 --- a/tile-crypto/src/main/java/com/power4j/tile/crypto/core/TextCipherBuilder.java +++ b/tile-crypto/src/main/java/com/power4j/tile/crypto/core/TextCipherBuilder.java @@ -16,7 +16,7 @@ package com.power4j.tile.crypto.core; -import com.power4j.tile.crypto.bc.BouncyCastleBlockCipher; +import com.power4j.tile.crypto.bc.BouncyCastleQuickCipher; import com.power4j.tile.crypto.bc.GlobalBouncyCastleProvider; import com.power4j.tile.crypto.bc.Spec; import com.power4j.tile.crypto.core.encode.Base64Encoder; @@ -39,18 +39,18 @@ public class TextCipherBuilder { private static final Function HASH_NONE = b -> new byte[0]; - private final BlockCipherBuilder blockCipherBuilder; + private final QuickCipherBuilder quickCipherBuilder; private BufferEncoder inputEncoder; private BufferEncoder outputEncoder; - public TextCipherBuilder(BlockCipherBuilder blockCipherBuilder) { - this.blockCipherBuilder = blockCipherBuilder; + public TextCipherBuilder(QuickCipherBuilder quickCipherBuilder) { + this.quickCipherBuilder = quickCipherBuilder; } public static TextCipherBuilder of(String algorithmName, String mode, String padding) { - BlockCipherBuilder builder = BlockCipherBuilder.algorithm(algorithmName).mode(mode).padding(padding); + QuickCipherBuilder builder = QuickCipherBuilder.algorithm(algorithmName).mode(mode).padding(padding); return new TextCipherBuilder(builder); } @@ -70,8 +70,8 @@ public static TextCipherBuilder sm4Ofb() { return of(Spec.ALGORITHM_SM4, Spec.MODE_OFB, Spec.PADDING_NO_PADDING); } - public TextCipherBuilder cipher(Consumer consumer) { - consumer.accept(blockCipherBuilder); + public TextCipherBuilder cipher(Consumer consumer) { + consumer.accept(quickCipherBuilder); return this; } @@ -96,11 +96,11 @@ public TextCipherBuilder outputEncoding(BufferEncoder encoder) { } public TextCipherBuilder reversedEncoder() { - return new TextCipherBuilder(blockCipherBuilder).inputEncoding(outputEncoder).outputEncoding(inputEncoder); + return new TextCipherBuilder(quickCipherBuilder).inputEncoding(outputEncoder).outputEncoding(inputEncoder); } public TextCipher build() { - BouncyCastleBlockCipher cipher = blockCipherBuilder.build(); + BouncyCastleQuickCipher cipher = quickCipherBuilder.build(); return BouncyCastleTextCipher.builder() .cipher(cipher) @@ -142,7 +142,7 @@ static class BouncyCastleTextCipher implements TextCipher { private final BufferEncoder outputEncoder; - private final BouncyCastleBlockCipher cipher; + private final BouncyCastleQuickCipher cipher; @Override public String encrypt(String data) throws GeneralCryptoException { @@ -150,17 +150,17 @@ public String encrypt(String data) throws GeneralCryptoException { } @Override - public CiphertextEnvelope encryptEnvelope(String data) throws GeneralCryptoException { - CipherBlobEnvelope envelope = cipher.encryptEnvelope(inputEncoder.decode(data)); - String iv = envelope.getIvOptional().map(outputEncoder::encode).orElse(null); - return CiphertextEnvelope.builder() + public CiphertextDetails encryptEnvelope(String data) throws GeneralCryptoException { + CipherBlobDetails details = cipher.encrypt(inputEncoder.decode(data)); + String iv = details.getIvOptional().map(outputEncoder::encode).orElse(null); + return CiphertextDetails.builder() .encoding(outputEncoder.algorithm()) - .algorithm(envelope.getAlgorithm()) - .mode(envelope.getMode()) - .padding(envelope.getPadding()) - .ciphertext(outputEncoder.encode(envelope.getCipher())) + .algorithm(details.getAlgorithm()) + .mode(details.getMode()) + .padding(details.getPadding()) + .ciphertext(outputEncoder.encode(details.getCipher())) .iv(iv) - .checksum(outputEncoder.encode(envelope.getChecksum())) + .checksum(outputEncoder.encode(details.getChecksum())) .build(); } @@ -170,7 +170,7 @@ public String decrypt(String data) throws GeneralCryptoException { } private byte[] encryptData(byte[] data) throws GeneralCryptoException { - return cipher.encrypt(data); + return cipher.encrypt(data).getCipher(); } private byte[] decryptData(byte[] data) throws GeneralCryptoException { diff --git a/tile-crypto/src/main/java/com/power4j/tile/crypto/core/TextDecrypt.java b/tile-crypto/src/main/java/com/power4j/tile/crypto/core/TextDec.java similarity index 96% rename from tile-crypto/src/main/java/com/power4j/tile/crypto/core/TextDecrypt.java rename to tile-crypto/src/main/java/com/power4j/tile/crypto/core/TextDec.java index d3f526e..293050d 100644 --- a/tile-crypto/src/main/java/com/power4j/tile/crypto/core/TextDecrypt.java +++ b/tile-crypto/src/main/java/com/power4j/tile/crypto/core/TextDec.java @@ -22,7 +22,7 @@ * @author CJ (power4j@outlook.com) * @since 1.6 */ -public interface TextDecrypt { +public interface TextDec { /** * 解密 diff --git a/tile-crypto/src/main/java/com/power4j/tile/crypto/core/TextEncrypt.java b/tile-crypto/src/main/java/com/power4j/tile/crypto/core/TextEnc.java similarity index 90% rename from tile-crypto/src/main/java/com/power4j/tile/crypto/core/TextEncrypt.java rename to tile-crypto/src/main/java/com/power4j/tile/crypto/core/TextEnc.java index b4def1c..96c5abc 100644 --- a/tile-crypto/src/main/java/com/power4j/tile/crypto/core/TextEncrypt.java +++ b/tile-crypto/src/main/java/com/power4j/tile/crypto/core/TextEnc.java @@ -22,7 +22,7 @@ * @author CJ (power4j@outlook.com) * @since 1.6 */ -public interface TextEncrypt { +public interface TextEnc { /** * 加密 @@ -40,6 +40,6 @@ default String encrypt(String data) throws GeneralCryptoException { * @return CiphertextEnvelope * @throws GeneralCryptoException */ - CiphertextEnvelope encryptEnvelope(String data) throws GeneralCryptoException; + CiphertextDetails encryptEnvelope(String data) throws GeneralCryptoException; } diff --git a/tile-crypto/src/main/java/com/power4j/tile/crypto/core/UncheckedCipher.java b/tile-crypto/src/main/java/com/power4j/tile/crypto/core/UncheckedCipher.java new file mode 100644 index 0000000..1f83ee0 --- /dev/null +++ b/tile-crypto/src/main/java/com/power4j/tile/crypto/core/UncheckedCipher.java @@ -0,0 +1,46 @@ +/* + * Copyright 2019-2024 the original author or authors. + * + * 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 + * + * https://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 com.power4j.tile.crypto.core; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.springframework.lang.Nullable; + +/** + * @author CJ (power4j@outlook.com) + * @since 1.0 + */ +@Getter +@RequiredArgsConstructor +public class UncheckedCipher { + + private final Slice cipher; + + private final Slice checksum; + + public static UncheckedCipher of(byte[] cipher, @Nullable byte[] checksum) { + if (checksum == null) { + checksum = new byte[0]; + } + return new UncheckedCipher(Slice.wrap(cipher), Slice.wrap(checksum)); + } + + public static UncheckedCipher of(byte[] cipher) { + return of(cipher, null); + } + +} diff --git a/tile-crypto/src/main/java/com/power4j/tile/crypto/dynamic/DecryptInfo.java b/tile-crypto/src/main/java/com/power4j/tile/crypto/dynamic/DecryptInfo.java index 218e691..9a51fa4 100644 --- a/tile-crypto/src/main/java/com/power4j/tile/crypto/dynamic/DecryptInfo.java +++ b/tile-crypto/src/main/java/com/power4j/tile/crypto/dynamic/DecryptInfo.java @@ -16,6 +16,7 @@ package com.power4j.tile.crypto.dynamic; +import com.power4j.tile.crypto.core.Slice; import lombok.Builder; import lombok.Getter; @@ -31,8 +32,8 @@ public class DecryptInfo { private final String keyTag; - private final byte[] data; + private final Slice data; - private final byte[] checksum; + private final Slice checksum; } diff --git a/tile-crypto/src/main/java/com/power4j/tile/crypto/dynamic/DynamicDecrypt.java b/tile-crypto/src/main/java/com/power4j/tile/crypto/dynamic/DynamicDecrypt.java index 8440171..f262384 100644 --- a/tile-crypto/src/main/java/com/power4j/tile/crypto/dynamic/DynamicDecrypt.java +++ b/tile-crypto/src/main/java/com/power4j/tile/crypto/dynamic/DynamicDecrypt.java @@ -16,8 +16,8 @@ package com.power4j.tile.crypto.dynamic; -import com.power4j.tile.crypto.core.CipherBlob; import com.power4j.tile.crypto.core.GeneralCryptoException; +import com.power4j.tile.crypto.core.UncheckedCipher; /** * @author CJ (power4j@outlook.com) @@ -31,6 +31,6 @@ public interface DynamicDecrypt { * @return 返回解密结果 * @throws GeneralCryptoException */ - DynamicDecryptResult decrypt(CipherBlob store) throws GeneralCryptoException; + DynamicDecryptResult decrypt(UncheckedCipher store) throws GeneralCryptoException; } diff --git a/tile-crypto/src/main/java/com/power4j/tile/crypto/dynamic/KeyPool.java b/tile-crypto/src/main/java/com/power4j/tile/crypto/dynamic/KeyPool.java index a5719df..d8e6e0d 100644 --- a/tile-crypto/src/main/java/com/power4j/tile/crypto/dynamic/KeyPool.java +++ b/tile-crypto/src/main/java/com/power4j/tile/crypto/dynamic/KeyPool.java @@ -27,16 +27,16 @@ public interface KeyPool { /** - * 获取一个加密密钥 + * 获取一个密钥 * @return 密钥信息,没有可用密钥返回 {@link Optional#empty()} } */ - Optional encryptKey(long param); + DynamicKey one(long param); /** - * 获取一组解密密钥 + * 获取一组密钥 * @param param 密钥参数 * @return 密钥列表,没有可用密钥返回 {@link Collections#emptyList() } */ - List decryptKeys(long param); + List some(long param); } diff --git a/tile-crypto/src/main/java/com/power4j/tile/crypto/dynamic/Pools.java b/tile-crypto/src/main/java/com/power4j/tile/crypto/dynamic/Pools.java index b087ab4..a70bebe 100644 --- a/tile-crypto/src/main/java/com/power4j/tile/crypto/dynamic/Pools.java +++ b/tile-crypto/src/main/java/com/power4j/tile/crypto/dynamic/Pools.java @@ -16,6 +16,7 @@ package com.power4j.tile.crypto.dynamic; +import com.power4j.tile.crypto.core.GeneralCryptoException; import lombok.experimental.UtilityClass; import java.util.ArrayList; @@ -23,7 +24,6 @@ import java.util.Collection; import java.util.Collections; import java.util.List; -import java.util.Optional; import java.util.concurrent.atomic.AtomicLong; /** @@ -72,12 +72,12 @@ public FixedPool(long id, byte[] key) { } @Override - public Optional encryptKey(long param) { - return Optional.of(key); + public DynamicKey one(long param) { + return key; } @Override - public List decryptKeys(long param) { + public List some(long param) { return Collections.singletonList(key); } @@ -99,13 +99,13 @@ static class RotationPool implements KeyPool { } @Override - public Optional encryptKey(long param) { + public DynamicKey one(long param) { int pos = (int) (index.getAndIncrement() % keys.length); - return Optional.of(keys[pos]); + return keys[pos]; } @Override - public List decryptKeys(long param) { + public List some(long param) { return Arrays.asList(keys); } @@ -116,13 +116,13 @@ static class EmptyPool implements KeyPool { static final EmptyPool INSTANCE = new EmptyPool(); @Override - public Optional encryptKey(long param) { - return Optional.empty(); + public DynamicKey one(long param) { + throw new GeneralCryptoException("No key available"); } @Override - public List decryptKeys(long param) { - return Collections.emptyList(); + public List some(long param) { + throw new GeneralCryptoException("No key available"); } } diff --git a/tile-crypto/src/main/java/com/power4j/tile/crypto/dynamic/SimpleDynamicDecrypt.java b/tile-crypto/src/main/java/com/power4j/tile/crypto/dynamic/SimpleDynamicDecrypt.java index 8940371..6e93b9d 100644 --- a/tile-crypto/src/main/java/com/power4j/tile/crypto/dynamic/SimpleDynamicDecrypt.java +++ b/tile-crypto/src/main/java/com/power4j/tile/crypto/dynamic/SimpleDynamicDecrypt.java @@ -16,16 +16,16 @@ package com.power4j.tile.crypto.dynamic; -import com.power4j.tile.crypto.core.BlockCipher; -import com.power4j.tile.crypto.core.BlockCipherBuilder; -import com.power4j.tile.crypto.core.CipherBlob; import com.power4j.tile.crypto.core.GeneralCryptoException; +import com.power4j.tile.crypto.core.QuickCipher; +import com.power4j.tile.crypto.core.QuickCipherBuilder; +import com.power4j.tile.crypto.core.Slice; +import com.power4j.tile.crypto.core.UncheckedCipher; import com.power4j.tile.crypto.core.Verified; import lombok.RequiredArgsConstructor; import org.springframework.lang.Nullable; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; import java.util.function.Function; import java.util.function.Supplier; @@ -53,10 +53,10 @@ public class SimpleDynamicDecrypt implements DynamicDecrypt { private final Supplier paramterSupplier; @Override - public DynamicDecryptResult decrypt(CipherBlob store) { + public DynamicDecryptResult decrypt(UncheckedCipher store) { final long timestamp = paramterSupplier.get(); - List keyList = keyPool.decryptKeys(timestamp); - List ivList = ivPool.decryptKeys(timestamp); + List keyList = keyPool.some(timestamp); + List ivList = ivPool.some(timestamp); if (keyList.isEmpty()) { throw new GeneralCryptoException("No key found"); } @@ -83,23 +83,23 @@ public DynamicDecryptResult decrypt(CipherBlob store) { return DynamicDecryptResult.fail(tried); } - protected DecryptInfo tryOne(CipherBlob store, DynamicKey key, @Nullable DynamicKey iv) { + protected DecryptInfo tryOne(UncheckedCipher input, DynamicKey key, @Nullable DynamicKey iv) { try { - BlockCipher cipher = BlockCipherBuilder.algorithm(algorithmName) + QuickCipher cipher = QuickCipherBuilder.algorithm(algorithmName) .mode(mode) .padding(padding) .secretKey(key.getKey()) .ivParameter(iv == null ? null : iv.getKey()) .checksumCalculator(checksumCalculator) .checksumVerifier( - (cipherBlob, bytes) -> Arrays.equals(cipherBlob.getChecksum(), checksumCalculator.apply(bytes))) + (cipherBlob, bytes) -> cipherBlob.getChecksum().dataEquals(checksumCalculator.apply(bytes))) .build(); - Verified verified = cipher.decrypt(store, false); + Verified verified = cipher.decrypt(input, false); return DecryptInfo.builder() .matched(verified.isPass()) .keyTag(key.getTag()) - .checksum(store.getChecksum()) - .data(verified.getData()) + .checksum(input.getChecksum()) + .data(Slice.wrap(verified.getData())) .build(); } catch (GeneralCryptoException e) { diff --git a/tile-crypto/src/main/java/com/power4j/tile/crypto/dynamic/TimeBasedPool.java b/tile-crypto/src/main/java/com/power4j/tile/crypto/dynamic/TimeBasedPool.java index 700651a..97debdd 100644 --- a/tile-crypto/src/main/java/com/power4j/tile/crypto/dynamic/TimeBasedPool.java +++ b/tile-crypto/src/main/java/com/power4j/tile/crypto/dynamic/TimeBasedPool.java @@ -22,7 +22,6 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; -import java.util.Optional; /** * @author CJ (power4j@outlook.com) @@ -56,12 +55,12 @@ public static Builder ofSize(int keySize) { } @Override - public Optional encryptKey(long param) { - return Optional.of(atOffset(param, 0)); + public DynamicKey one(long param) { + return atOffset(param, 0); } @Override - public List decryptKeys(long param) { + public List some(long param) { List keys = new ArrayList<>(windowSize * 2 + 1); keys.add(atOffset(param, 0)); for (int i = 1; i <= windowSize; i++) { diff --git a/tile-crypto/src/main/java/com/power4j/tile/crypto/utils/CryptoUtil.java b/tile-crypto/src/main/java/com/power4j/tile/crypto/utils/CryptoUtil.java index 6c09f7a..cbed309 100644 --- a/tile-crypto/src/main/java/com/power4j/tile/crypto/utils/CryptoUtil.java +++ b/tile-crypto/src/main/java/com/power4j/tile/crypto/utils/CryptoUtil.java @@ -17,16 +17,16 @@ package com.power4j.tile.crypto.utils; import com.power4j.tile.crypto.bc.GlobalBouncyCastleProvider; -import com.power4j.tile.crypto.core.CipherBlob; import com.power4j.tile.crypto.core.GeneralCryptoException; +import com.power4j.tile.crypto.core.UncheckedCipher; import lombok.experimental.UtilityClass; import org.apache.commons.codec.binary.Hex; import org.springframework.lang.Nullable; import javax.crypto.Cipher; +import javax.crypto.NoSuchPaddingException; import javax.crypto.spec.SecretKeySpec; -import java.security.GeneralSecurityException; -import java.util.Arrays; +import java.security.NoSuchAlgorithmException; import java.util.function.BiFunction; import java.util.function.Function; @@ -39,15 +39,20 @@ public class CryptoUtil { public static final Function SM3_CHECKSUM_CALCULATOR = (b) -> Sm3Util.hash(b, null); - public static final BiFunction SM3_CHECKSUM_VERIFIER = (cipherBlob, b) -> Arrays - .equals(SM3_CHECKSUM_CALCULATOR.apply(b), cipherBlob.getChecksum()); + public static final BiFunction SM3_CHECKSUM_VERIFIER = (input, + b) -> input.getChecksum().dataEquals(SM3_CHECKSUM_CALCULATOR.apply(b)); - public static final BiFunction IGNORED_CHECKSUM_VERIFIER = (blob, bytes) -> true; + public static final BiFunction IGNORED_CHECKSUM_VERIFIER = (blob, bytes) -> true; public static final Function EMPTY_CHECKSUM_CALCULATOR = b -> new byte[0]; - public Cipher createCipher(String transformation) throws GeneralSecurityException { - return Cipher.getInstance(transformation, GlobalBouncyCastleProvider.INSTANCE.getProvider()); + public Cipher createCipher(String transformation) { + try { + return Cipher.getInstance(transformation, GlobalBouncyCastleProvider.INSTANCE.getProvider()); + } + catch (NoSuchAlgorithmException | NoSuchPaddingException e) { + throw new GeneralCryptoException(e); + } } public SecretKeySpec createKey(byte[] key, String algorithmName) { diff --git a/tile-crypto/src/main/java/com/power4j/tile/crypto/utils/Sm4Util.java b/tile-crypto/src/main/java/com/power4j/tile/crypto/utils/Sm4Util.java index 33bbada..b58d16b 100644 --- a/tile-crypto/src/main/java/com/power4j/tile/crypto/utils/Sm4Util.java +++ b/tile-crypto/src/main/java/com/power4j/tile/crypto/utils/Sm4Util.java @@ -17,9 +17,9 @@ package com.power4j.tile.crypto.utils; import com.power4j.tile.crypto.bc.Spec; -import com.power4j.tile.crypto.core.BlockCipher; -import com.power4j.tile.crypto.core.BlockCipherBuilder; import com.power4j.tile.crypto.core.GeneralCryptoException; +import com.power4j.tile.crypto.core.QuickCipher; +import com.power4j.tile.crypto.core.QuickCipherBuilder; import lombok.experimental.UtilityClass; /** @@ -40,11 +40,11 @@ public class Sm4Util { public static final int BLOCK_SIZE = 16; - public BlockCipher useEcbWithPadding(byte[] key) throws GeneralCryptoException { + public QuickCipher useEcbWithPadding(byte[] key) throws GeneralCryptoException { return builderWithVerifySupport(Spec.MODE_ECB, Spec.PADDING_PKCS7).secretKey(key).build(); } - public BlockCipher useCbcWithPadding(byte[] key, byte[] iv) throws GeneralCryptoException { + public QuickCipher useCbcWithPadding(byte[] key, byte[] iv) throws GeneralCryptoException { if (iv.length != BLOCK_SIZE) { throw new IllegalArgumentException( String.format("Invalid IV length: %d, should be %d", iv.length, BLOCK_SIZE)); @@ -52,7 +52,7 @@ public BlockCipher useCbcWithPadding(byte[] key, byte[] iv) throws GeneralCrypto return builderWithVerifySupport(Spec.MODE_CBC, Spec.PADDING_PKCS7).secretKey(key).ivParameter(iv).build(); } - public BlockCipher useCfb(byte[] key, byte[] iv) throws GeneralCryptoException { + public QuickCipher useCfb(byte[] key, byte[] iv) throws GeneralCryptoException { if (iv.length != BLOCK_SIZE) { throw new IllegalArgumentException( String.format("Invalid IV length: %d, should be %d", iv.length, BLOCK_SIZE)); @@ -60,7 +60,7 @@ public BlockCipher useCfb(byte[] key, byte[] iv) throws GeneralCryptoException { return builderWithVerifySupport(Spec.MODE_CFB, Spec.PADDING_NO_PADDING).secretKey(key).ivParameter(iv).build(); } - public BlockCipher useOfb(byte[] key, byte[] iv) throws GeneralCryptoException { + public QuickCipher useOfb(byte[] key, byte[] iv) throws GeneralCryptoException { if (iv.length != BLOCK_SIZE) { throw new IllegalArgumentException( String.format("Invalid IV length: %d, should be %d", iv.length, BLOCK_SIZE)); @@ -68,34 +68,34 @@ public BlockCipher useOfb(byte[] key, byte[] iv) throws GeneralCryptoException { return builderWithVerifySupport(Spec.MODE_OFB, Spec.PADDING_NO_PADDING).secretKey(key).ivParameter(iv).build(); } - public BlockCipher useEcbWithPadding(String hexKey) throws GeneralCryptoException { + public QuickCipher useEcbWithPadding(String hexKey) throws GeneralCryptoException { byte[] key = CryptoUtil.decodeHex(hexKey, null); return useEcbWithPadding(key); } - public BlockCipher useCbcWithPadding(String hexKey, String hexIv) throws GeneralCryptoException { + public QuickCipher useCbcWithPadding(String hexKey, String hexIv) throws GeneralCryptoException { byte[] key = CryptoUtil.decodeHex(hexKey, null); byte[] iv = CryptoUtil.decodeHex(hexIv, null); return useCbcWithPadding(key, iv); } - public BlockCipher useCfb(String hexKey, String hexIv) throws GeneralCryptoException { + public QuickCipher useCfb(String hexKey, String hexIv) throws GeneralCryptoException { byte[] key = CryptoUtil.decodeHex(hexKey, null); byte[] iv = CryptoUtil.decodeHex(hexIv, null); return useCfb(key, iv); } - public BlockCipher useOfb(String hexKey, String hexIv) throws GeneralCryptoException { + public QuickCipher useOfb(String hexKey, String hexIv) throws GeneralCryptoException { byte[] key = CryptoUtil.decodeHex(hexKey, null); byte[] iv = CryptoUtil.decodeHex(hexIv, null); return useOfb(key, iv); } - public BlockCipherBuilder builder(String mode, String padding) { - return BlockCipherBuilder.algorithm(Spec.ALGORITHM_SM4).mode(mode).padding(padding); + public QuickCipherBuilder builder(String mode, String padding) { + return QuickCipherBuilder.algorithm(Spec.ALGORITHM_SM4).mode(mode).padding(padding); } - public BlockCipherBuilder builderWithVerifySupport(String mode, String padding) { + public QuickCipherBuilder builderWithVerifySupport(String mode, String padding) { return builder(mode, padding).sm3ChecksumCalculator().sm3ChecksumVerifier(); } diff --git a/tile-crypto/src/test/java/com/power4j/tile/crypto/bc/BouncyCastleBlockCipherTest.java b/tile-crypto/src/test/java/com/power4j/tile/crypto/bc/BouncyCastleQuickCipherTest.java similarity index 70% rename from tile-crypto/src/test/java/com/power4j/tile/crypto/bc/BouncyCastleBlockCipherTest.java rename to tile-crypto/src/test/java/com/power4j/tile/crypto/bc/BouncyCastleQuickCipherTest.java index aeb699a..2360b0f 100644 --- a/tile-crypto/src/test/java/com/power4j/tile/crypto/bc/BouncyCastleBlockCipherTest.java +++ b/tile-crypto/src/test/java/com/power4j/tile/crypto/bc/BouncyCastleQuickCipherTest.java @@ -16,8 +16,8 @@ package com.power4j.tile.crypto.bc; -import com.power4j.tile.crypto.core.CipherBlob; -import com.power4j.tile.crypto.core.CipherBlobEnvelope; +import com.power4j.tile.crypto.core.CipherBlobDetails; +import com.power4j.tile.crypto.core.UncheckedCipher; import com.power4j.tile.crypto.core.Verified; import com.power4j.tile.crypto.utils.Sm4Util; import org.junit.jupiter.api.Assertions; @@ -31,7 +31,7 @@ * @author CJ (power4j@outlook.com) * @since 1.0 */ -class BouncyCastleBlockCipherTest { +class BouncyCastleQuickCipherTest { private final byte[] testKey = new byte[] { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10 }; @@ -42,21 +42,21 @@ class BouncyCastleBlockCipherTest { @Test void encrypt() { byte[] plain = "hello".getBytes(StandardCharsets.UTF_8); - BouncyCastleBlockCipher cipher = Sm4Util.builder(Spec.MODE_CBC, Spec.PADDING_PKCS7) + BouncyCastleQuickCipher cipher = Sm4Util.builder(Spec.MODE_CBC, Spec.PADDING_PKCS7) .secretKey(testKey) .ivParameter(testIv) .checksumCalculator(null) .build(); - CipherBlobEnvelope envelope = cipher.encryptEnvelope(plain); + CipherBlobDetails details = cipher.encrypt(plain); - Assertions.assertNotNull(envelope.getAlgorithm()); - Assertions.assertNotNull(envelope.getMode()); - Assertions.assertNotNull(envelope.getPadding()); - Assertions.assertNotNull(envelope.getIv()); - Assertions.assertNotNull(envelope.getCipher()); - Assertions.assertNotNull(envelope.getChecksum()); + Assertions.assertNotNull(details.getAlgorithm()); + Assertions.assertNotNull(details.getMode()); + Assertions.assertNotNull(details.getPadding()); + Assertions.assertNotNull(details.getIv()); + Assertions.assertNotNull(details.getCipher()); + Assertions.assertNotNull(details.getChecksum()); - byte[] decrypted = cipher.decrypt(envelope.getCipher()); + byte[] decrypted = cipher.decrypt(details.getCipher()); Assertions.assertArrayEquals(plain, decrypted); } @@ -65,14 +65,14 @@ void decryptWithCheck() { byte[] plain = "hello".getBytes(StandardCharsets.UTF_8); Function hashFunc = (b) -> Arrays.copyOf(b, 8); - BouncyCastleBlockCipher cipher = Sm4Util.builder(Spec.MODE_CBC, Spec.PADDING_PKCS7) + BouncyCastleQuickCipher cipher = Sm4Util.builder(Spec.MODE_CBC, Spec.PADDING_PKCS7) .secretKey(testKey) .ivParameter(testIv) .checksumCalculator(hashFunc) .build(); - CipherBlobEnvelope envelope = cipher.encryptEnvelope(plain); + CipherBlobDetails details = cipher.encrypt(plain); - CipherBlob store = new CipherBlob(envelope.getCipher(), envelope.getChecksum()); + UncheckedCipher store = UncheckedCipher.of(details.getCipher(), details.getChecksum()); Verified verified = cipher.decrypt(store, false); Assertions.assertTrue(verified.isPass()); Assertions.assertArrayEquals(plain, verified.getData()); diff --git a/tile-crypto/src/test/java/com/power4j/tile/crypto/core/SliceTest.java b/tile-crypto/src/test/java/com/power4j/tile/crypto/core/SliceTest.java new file mode 100644 index 0000000..c3582c6 --- /dev/null +++ b/tile-crypto/src/test/java/com/power4j/tile/crypto/core/SliceTest.java @@ -0,0 +1,90 @@ +/* + * Copyright 2019-2024 the original author or authors. + * + * 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 + * + * https://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 com.power4j.tile.crypto.core; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * @author CJ (power4j@outlook.com) + * @since 1.0 + */ +class SliceTest { + + @Test + void range() { + Slice bytes = Slice.range(new byte[] { 1, 2, 3 }, 1, 2); + assertArrayEquals(new byte[] { 2, 3 }, bytes.unwrap()); + } + + @Test + void remaining() { + Slice bytes = Slice.remaining(new byte[] { 1, 2, 3 }, 1); + assertArrayEquals(new byte[] { 2, 3 }, bytes.unwrap()); + } + + @Test + void copyOf() { + Slice bytes = Slice.copyOf(new byte[] { 1, 2, 3 }); + assertArrayEquals(new byte[] { 1, 2, 3 }, bytes.unwrap()); + } + + @Test + void copyOfRange() { + Slice bytes = Slice.copyOfRange(new byte[] { 1, 2, 3 }, 1, 2); + assertArrayEquals(new byte[] { 2, 3 }, bytes.unwrap()); + } + + @Test + void copyOfRemaining() { + Slice bytes = Slice.copyOfRemaining(new byte[] { 1, 2, 3 }, 1); + assertArrayEquals(new byte[] { 2, 3 }, bytes.unwrap()); + } + + @Test + void unwrap() { + Slice bytes = Slice.wrap(new byte[] { 1, 2, 3 }); + assertArrayEquals(new byte[] { 1, 2, 3 }, bytes.unwrap()); + } + + @Test + void sub() { + Slice bytes = Slice.wrap(new byte[] { 1, 2, 3 }).sub(1, 2); + assertArrayEquals(new byte[] { 2, 3 }, bytes.unwrap()); + } + + @Test + void dataEquals() { + Slice one = Slice.wrap(new byte[] { 1, 2, 3 }); + Slice two = Slice.wrap(new byte[] { 1, 2, 3 }); + assertTrue(one.dataEquals(two)); + assertTrue(one.dataEquals(new byte[] { 1, 2, 3 })); + assertFalse(one.dataEquals(new byte[] { 1, 2, 4 })); + } + + @Test + void shouldThrowIllegalArgumentException() { + assertThrows(IllegalArgumentException.class, () -> Slice.range(new byte[1], -1, 0)); + assertThrows(IllegalArgumentException.class, () -> Slice.range(new byte[1], 0, -1)); + assertThrows(IllegalArgumentException.class, () -> Slice.range(new byte[1], 1, 1)); + } + +} diff --git a/tile-crypto/src/test/java/com/power4j/tile/crypto/core/Sm4TextCipherTest.java b/tile-crypto/src/test/java/com/power4j/tile/crypto/core/Sm4TextCipherTest.java index bed0fc1..d412d58 100644 --- a/tile-crypto/src/test/java/com/power4j/tile/crypto/core/Sm4TextCipherTest.java +++ b/tile-crypto/src/test/java/com/power4j/tile/crypto/core/Sm4TextCipherTest.java @@ -109,7 +109,7 @@ void envelopeTest() { .cipher(c -> c.secretKeyHex(key).ivParameterHex(iv).sm3ChecksumCalculator()) .inputEncoding(BufferEncoding.UTF_8) .outputEncoding(BufferEncoding.HEX); - CiphertextEnvelope envelope = builder.build().encryptEnvelope(plain); + CiphertextDetails envelope = builder.build().encryptEnvelope(plain); System.out.println("encrypted envelope = " + envelope); Assertions.assertNotNull(envelope.getEncoding()); Assertions.assertNotNull(envelope.getAlgorithm()); diff --git a/tile-crypto/src/test/java/com/power4j/tile/crypto/dynamic/SimpleDynamicDecryptTest.java b/tile-crypto/src/test/java/com/power4j/tile/crypto/dynamic/SimpleDynamicDecryptTest.java index f935e9a..8dc1ee4 100644 --- a/tile-crypto/src/test/java/com/power4j/tile/crypto/dynamic/SimpleDynamicDecryptTest.java +++ b/tile-crypto/src/test/java/com/power4j/tile/crypto/dynamic/SimpleDynamicDecryptTest.java @@ -17,9 +17,9 @@ package com.power4j.tile.crypto.dynamic; import com.power4j.tile.crypto.bc.Spec; -import com.power4j.tile.crypto.core.BlockCipher; -import com.power4j.tile.crypto.core.CipherBlob; -import com.power4j.tile.crypto.core.CipherBlobEnvelope; +import com.power4j.tile.crypto.core.CipherBlobDetails; +import com.power4j.tile.crypto.core.QuickCipher; +import com.power4j.tile.crypto.core.UncheckedCipher; import com.power4j.tile.crypto.utils.Sm4Util; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; @@ -58,18 +58,18 @@ void rotationDecryptTest() { .ivPool(Pools.fixed(testIv)) .simple(); - BlockCipher enc = Sm4Util.builder(Spec.MODE_CBC, Spec.PADDING_PKCS7) + QuickCipher enc = Sm4Util.builder(Spec.MODE_CBC, Spec.PADDING_PKCS7) .secretKey(testKey3) .ivParameter(testIv) .checksumCalculator(checksumCalculator) .build(); - CipherBlobEnvelope envelope = enc.encryptEnvelope(plain); - CipherBlob store = new CipherBlob(envelope.getCipher(), envelope.getChecksum()); + CipherBlobDetails details = enc.encrypt(plain); + UncheckedCipher store = UncheckedCipher.of(details.getCipher(), details.getChecksum()); DynamicDecryptResult result = dec.decrypt(store); Assertions.assertTrue(result.success()); Assertions.assertEquals(3, result.getTried().size()); - Assertions.assertArrayEquals(plain, result.requiredMatched().getData()); + Assertions.assertTrue(result.requiredMatched().getData().dataEquals(plain)); } @Test @@ -89,25 +89,25 @@ void timeBasedDecryptTest() { .keyPool(pool) .ivPool(Pools.fixed(testIv)); - BlockCipher enc = Sm4Util.builder(Spec.MODE_CBC, Spec.PADDING_PKCS7) - .secretKey(testKey3) + QuickCipher enc = Sm4Util.builder(Spec.MODE_CBC, Spec.PADDING_PKCS7) + .secretKey(pool.one(time).getKey()) .ivParameter(testIv) .checksumCalculator(checksumCalculator) .build(); - CipherBlobEnvelope envelope = enc.encryptEnvelope(plain); - CipherBlob store = new CipherBlob(envelope.getCipher(), envelope.getChecksum()); + CipherBlobDetails details = enc.encrypt(plain); + UncheckedCipher store = UncheckedCipher.of(details.getCipher(), details.getChecksum()); DynamicDecryptResult result = builder.parameterSupplier(() -> time).simple().decrypt(store); Assertions.assertTrue(result.success()); - Assertions.assertArrayEquals(plain, result.requiredMatched().getData()); + Assertions.assertTrue(result.requiredMatched().getData().dataEquals(plain)); result = builder.parameterSupplier(() -> time + intervalSeconds * 1000 * windowSize).simple().decrypt(store); Assertions.assertTrue(result.success()); - Assertions.assertArrayEquals(plain, result.requiredMatched().getData()); + Assertions.assertTrue(result.requiredMatched().getData().dataEquals(plain)); result = builder.parameterSupplier(() -> time - intervalSeconds * 1000 * windowSize).simple().decrypt(store); Assertions.assertTrue(result.success()); - Assertions.assertArrayEquals(plain, result.requiredMatched().getData()); + Assertions.assertTrue(result.requiredMatched().getData().dataEquals(plain)); } } diff --git a/tile-crypto/src/test/java/com/power4j/tile/crypto/dynamic/TimeBasedPoolTest.java b/tile-crypto/src/test/java/com/power4j/tile/crypto/dynamic/TimeBasedPoolTest.java index 158f543..2b8b8e5 100644 --- a/tile-crypto/src/test/java/com/power4j/tile/crypto/dynamic/TimeBasedPoolTest.java +++ b/tile-crypto/src/test/java/com/power4j/tile/crypto/dynamic/TimeBasedPoolTest.java @@ -73,9 +73,9 @@ void keyPoolGeneratorTest() { .interval(Duration.ofMinutes(1)) .fillBytes(new byte[] { 1 }) .build(); - final List decryptKeys = pool.decryptKeys(input); + final List decryptKeys = pool.some(input); Assertions.assertEquals(11, decryptKeys.size()); - final DynamicKey encryptKey = pool.encryptKey(input).orElse(null); + final DynamicKey encryptKey = pool.one(input); Assertions.assertNotNull(encryptKey); Assertions.assertArrayEquals(encryptKey.getKey(), decryptKeys.get(0).getKey()); } diff --git a/tile-crypto/src/test/java/com/power4j/tile/crypto/utils/CryptoUtilTest.java b/tile-crypto/src/test/java/com/power4j/tile/crypto/utils/CryptoUtilTest.java index b028a7d..99273b1 100644 --- a/tile-crypto/src/test/java/com/power4j/tile/crypto/utils/CryptoUtilTest.java +++ b/tile-crypto/src/test/java/com/power4j/tile/crypto/utils/CryptoUtilTest.java @@ -20,8 +20,6 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; -import static org.junit.jupiter.api.Assertions.*; - /** * @author CJ (power4j@outlook.com) * @since 1.0 diff --git a/tile-crypto/src/test/java/com/power4j/tile/crypto/utils/Sm4UtilTest.java b/tile-crypto/src/test/java/com/power4j/tile/crypto/utils/Sm4UtilTest.java index c0148d8..f3a0e5b 100644 --- a/tile-crypto/src/test/java/com/power4j/tile/crypto/utils/Sm4UtilTest.java +++ b/tile-crypto/src/test/java/com/power4j/tile/crypto/utils/Sm4UtilTest.java @@ -16,7 +16,7 @@ package com.power4j.tile.crypto.utils; -import com.power4j.tile.crypto.core.BlockCipher; +import com.power4j.tile.crypto.core.QuickCipher; import org.bouncycastle.util.encoders.Hex; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; @@ -36,39 +36,39 @@ class Sm4UtilTest { @Test void testEcbWithPadding() { - BlockCipher sm4 = Sm4Util.useEcbWithPadding("2aa67d8833e28fa88b9ad09aaaa90619"); - byte[] enc = sm4.encrypt(Hex.decode("0001515641703792")); + QuickCipher sm4 = Sm4Util.useEcbWithPadding("2aa67d8833e28fa88b9ad09aaaa90619"); + byte[] enc = sm4.encrypt(Hex.decode("0001515641703792")).getCipher(); assertEquals("9e8078a5d816f67ed3a1d536294c8509", Hex.toHexString(enc)); byte[] plain = sm4.decrypt(enc); assertEquals("0001515641703792", Hex.toHexString(plain)); sm4 = Sm4Util.useEcbWithPadding("4aa697a7d8394925884767a5888a0c8a"); - String rootEnc = Hex.toHexString(sm4.encrypt("root".getBytes(StandardCharsets.UTF_8))); + String rootEnc = Hex.toHexString(sm4.encrypt("root".getBytes(StandardCharsets.UTF_8)).getCipher()); System.out.println("SM4 ECB Enc(root):" + rootEnc); Assertions.assertEquals("683ed60b4f4bca8ec7962742a9183ce4", rootEnc); } @Test void testCbcWithPadding() { - BlockCipher sm4 = Sm4Util.useCbcWithPadding("2aa67d8833e28fa88b9ad09aaaa90619", + QuickCipher sm4 = Sm4Util.useCbcWithPadding("2aa67d8833e28fa88b9ad09aaaa90619", "82ac066e232a6b19e203f531855a809e"); - byte[] enc = sm4.encrypt(Hex.decode("0001515641703792")); + byte[] enc = sm4.encrypt(Hex.decode("0001515641703792")).getCipher(); assertEquals("f702064157cf93cf3f589d0246388450", Hex.toHexString(enc)); byte[] plain = sm4.decrypt(enc); assertEquals("0001515641703792", Hex.toHexString(plain)); sm4 = Sm4Util.useCbcWithPadding("4aa697a7d8394925884767a5888a0c8a", "80c0b436a7a15b89e8622436c6d6f04e"); - String rootEnc = Hex.toHexString(sm4.encrypt("root".getBytes(StandardCharsets.UTF_8))); + String rootEnc = Hex.toHexString(sm4.encrypt("root".getBytes(StandardCharsets.UTF_8)).getCipher()); System.out.println("SM4 CBC Enc(root):" + rootEnc); Assertions.assertEquals("6172a52d39ab724781bc835775ce2a1b", rootEnc); } @Test void testOfb() { - BlockCipher sm4 = Sm4Util.useOfb("2aa67d8833e28fa88b9ad09aaaa90619", "ccbb066e232a6b19e203f531855a809e"); - byte[] enc = sm4.encrypt(Hex.decode("0001515641703792")); + QuickCipher sm4 = Sm4Util.useOfb("2aa67d8833e28fa88b9ad09aaaa90619", "ccbb066e232a6b19e203f531855a809e"); + byte[] enc = sm4.encrypt(Hex.decode("0001515641703792")).getCipher(); assertEquals("127ea1f04ce83d57", Hex.toHexString(enc)); byte[] plain = sm4.decrypt(enc); @@ -77,8 +77,8 @@ void testOfb() { @Test void testCfb() { - BlockCipher sm4 = Sm4Util.useCfb("2aa67d8833e28fa88b9ad09aaaa90619", "aabb066e232a6b19e203f531855a809e"); - byte[] enc = sm4.encrypt(Hex.decode("0001515641703792")); + QuickCipher sm4 = Sm4Util.useCfb("2aa67d8833e28fa88b9ad09aaaa90619", "aabb066e232a6b19e203f531855a809e"); + byte[] enc = sm4.encrypt(Hex.decode("0001515641703792")).getCipher(); assertEquals("caeeb2acacadf53a", Hex.toHexString(enc)); byte[] plain = sm4.decrypt(enc); @@ -87,7 +87,7 @@ void testCfb() { @Test void testHexProcess() { - BlockCipher sm4 = Sm4Util.useCbcWithPadding("2aa67d8833e28fa88b9ad09aaaa90619", + QuickCipher sm4 = Sm4Util.useCbcWithPadding("2aa67d8833e28fa88b9ad09aaaa90619", "82ac066e232a6b19e203f531855a809e"); byte[] plain = new byte[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 }; String enc = sm4.encryptHex(plain);