SHA256
refactor: перенос серверных модулей в папку SHiNE-server
This commit is contained in:
@@ -0,0 +1,29 @@
|
||||
plugins {
|
||||
id 'java'
|
||||
}
|
||||
|
||||
group = 'shine' // можешь поставить свой group
|
||||
version = '1.0.0'
|
||||
|
||||
java {
|
||||
toolchain {
|
||||
languageVersion = JavaLanguageVersion.of(17)
|
||||
}
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
// BouncyCastle для Ed25519 и SHA-256
|
||||
implementation 'org.bouncycastle:bcprov-jdk18on:1.78.1'
|
||||
|
||||
implementation "org.slf4j:slf4j-api:2.0.16" // вызов логгера
|
||||
|
||||
testImplementation 'org.junit.jupiter:junit-jupiter:5.11.0'
|
||||
}
|
||||
|
||||
test {
|
||||
useJUnitPlatform()
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
OUTFILE="all_files.txt"
|
||||
|
||||
# очищаем или создаём файл
|
||||
: > "$OUTFILE"
|
||||
|
||||
# собрать только *.java файлы и вывести их содержимое в файл
|
||||
find . -type f -name "*.java" | sort | while read -r f; do
|
||||
cat "$f" >> "$OUTFILE"
|
||||
echo >> "$OUTFILE" # пустая строка-разделитель
|
||||
done
|
||||
|
||||
# скопировать весь файл в буфер обмена (Wayland)
|
||||
wl-copy < "$OUTFILE"
|
||||
|
||||
echo "Готово!"
|
||||
echo "Все .java файлы собраны в $OUTFILE"
|
||||
echo "Содержимое скопировано в буфер обмена (Wayland)"
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
OUTFILE="all_files.txt"
|
||||
|
||||
# очищаем или создаём файл
|
||||
: > "$OUTFILE"
|
||||
|
||||
# собрать только *.java файлы и вывести их содержимое в файл
|
||||
find . -type f -name "*.java" | sort | while read -r f; do
|
||||
cat "$f" >> "$OUTFILE"
|
||||
echo >> "$OUTFILE" # пустая строка-разделитель
|
||||
done
|
||||
|
||||
# скопировать весь файл в буфер обмена (Wayland)
|
||||
wl-copy < "$OUTFILE"
|
||||
|
||||
echo "Готово!"
|
||||
echo "Все .java файлы собраны в $OUTFILE"
|
||||
echo "Содержимое скопировано в буфер обмена (Wayland)"
|
||||
@@ -0,0 +1,91 @@
|
||||
package utils.crypto;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Arrays;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* BchCryptoVerifier — проверка хэша и подписи Ed25519 для .bch сущностей.
|
||||
*.
|
||||
* Канонический пре-имидж:
|
||||
* [N] userLogin UTF-8 (без длины! строго как байты строки)
|
||||
* [8] blockchainId (big-endian long)
|
||||
* [32] prevHash32
|
||||
* [*] rawBytes (без подписи и без хэша)
|
||||
*.
|
||||
* Проверяем:
|
||||
* • hash32 == SHA-256(preimage)
|
||||
* • signature64 валидна как Ed25519(preimage, publicKey32)
|
||||
*/
|
||||
public final class BchCryptoVerifier {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(BchCryptoVerifier.class);
|
||||
|
||||
private BchCryptoVerifier() {}
|
||||
|
||||
public static boolean verifyAll(String userLogin,
|
||||
long blockchainId,
|
||||
byte[] prevHash32,
|
||||
byte[] rawBytes,
|
||||
byte[] signature64,
|
||||
byte[] hash32,
|
||||
byte[] publicKey32) {
|
||||
try {
|
||||
Objects.requireNonNull(userLogin, "userLogin");
|
||||
requireLen(prevHash32, 32, "prevHash32");
|
||||
requireLen(signature64, 64, "signature64");
|
||||
requireLen(hash32, 32, "hash32");
|
||||
requireLen(publicKey32, 32, "publicKey32");
|
||||
Objects.requireNonNull(rawBytes, "rawBytes");
|
||||
|
||||
byte[] preimage = buildPreimage(userLogin, blockchainId, prevHash32, rawBytes);
|
||||
|
||||
// 1) Проверка хэша (BC)
|
||||
byte[] calcHash = HashSHA256Util.sha256(preimage);
|
||||
boolean hashOk = Arrays.equals(calcHash, hash32);
|
||||
|
||||
// 2) Проверка подписи Ed25519
|
||||
boolean sigOk = Ed25519Util.verify(preimage, signature64, publicKey32);
|
||||
|
||||
if (!hashOk) log.warn("Hash mismatch: hash32 != SHA-256(preimage)");
|
||||
if (!sigOk) log.warn("Signature mismatch: Ed25519 verify failed");
|
||||
return hashOk && sigOk;
|
||||
} catch (IllegalArgumentException ex) {
|
||||
log.error("verifyAll: bad arguments", ex);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/** Собрать канонический пре-имидж без длины логина. */
|
||||
public static byte[] buildPreimage(String userLogin,
|
||||
long blockchainId,
|
||||
byte[] prevHash32,
|
||||
byte[] rawBytes) {
|
||||
Objects.requireNonNull(userLogin, "userLogin");
|
||||
Objects.requireNonNull(prevHash32, "prevHash32");
|
||||
Objects.requireNonNull(rawBytes, "rawBytes");
|
||||
|
||||
byte[] loginUtf8 = userLogin.getBytes(StandardCharsets.UTF_8);
|
||||
requireLen(prevHash32, 32, "prevHash32");
|
||||
|
||||
int capacity = loginUtf8.length + 8 + 32 + rawBytes.length;
|
||||
ByteBuffer buf = ByteBuffer.allocate(capacity).order(ByteOrder.BIG_ENDIAN);
|
||||
buf.put(loginUtf8);
|
||||
buf.putLong(blockchainId);
|
||||
buf.put(prevHash32);
|
||||
buf.put(rawBytes);
|
||||
return buf.array();
|
||||
}
|
||||
|
||||
private static void requireLen(byte[] arr, int len, String name) {
|
||||
if (arr == null) throw new IllegalArgumentException(name + " is null");
|
||||
if (arr.length != len) {
|
||||
throw new IllegalArgumentException(name + " length != " + len + " (got " + arr.length + ")");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package utils.crypto;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Arrays;
|
||||
|
||||
public final class CryptoSelfTest {
|
||||
|
||||
private CryptoSelfTest() {}
|
||||
|
||||
/**
|
||||
* Простой запуск: убедиться, что всё собрано и работает.
|
||||
* Выводит ключи в Base64, знак/проверка подписи — OK/FAIL.
|
||||
*/
|
||||
public static void main(String[] args) {
|
||||
System.out.println("=== Ed25519 self-check ===");
|
||||
|
||||
// 1) Генерация ключей
|
||||
byte[] priv = Ed25519Util.generatePrivateKey();
|
||||
byte[] pub = Ed25519Util.derivePublicKey(priv);
|
||||
|
||||
// 2) Конвертация в/из Base64 (чисто для демонстрации)
|
||||
String privB64 = Ed25519Util.keyToBase64(priv);
|
||||
String pubB64 = Ed25519Util.keyToBase64(pub);
|
||||
System.out.println("Private (seed) Base64: " + privB64);
|
||||
System.out.println("Public Base64 : " + pubB64);
|
||||
|
||||
byte[] priv2 = Ed25519Util.keyFromBase64(privB64);
|
||||
byte[] pub2 = Ed25519Util.keyFromBase64(pubB64);
|
||||
if (!Arrays.equals(priv, priv2) || !Arrays.equals(pub, pub2)) {
|
||||
throw new IllegalStateException("Base64 ⇆ bytes дала несовпадение (не должно случаться).");
|
||||
}
|
||||
|
||||
// 3) Подпись и проверка
|
||||
byte[] data = "Привет, мир Ed25519!".getBytes(StandardCharsets.UTF_8);
|
||||
byte[] sig = Ed25519Util.sign(data, priv);
|
||||
|
||||
boolean ok = Ed25519Util.verify(data, sig, pub);
|
||||
System.out.println("Verify OK? " + ok);
|
||||
|
||||
// 4) Негативный тест: портим данные
|
||||
byte[] bad = "Привет, мир Ed25519?".getBytes(StandardCharsets.UTF_8);
|
||||
boolean shouldFail = Ed25519Util.verify(bad, sig, pub);
|
||||
System.out.println("Verify on changed data (should be false): " + shouldFail);
|
||||
|
||||
if (!ok || shouldFail) {
|
||||
throw new IllegalStateException("Self-test failed.");
|
||||
}
|
||||
System.out.println("Self-test passed ✅");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,173 @@
|
||||
package utils.crypto;
|
||||
|
||||
import org.bouncycastle.crypto.params.Ed25519PrivateKeyParameters;
|
||||
import org.bouncycastle.crypto.params.Ed25519PublicKeyParameters;
|
||||
import org.bouncycastle.crypto.signers.Ed25519Signer;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Base64;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* ===============================================================
|
||||
* Ed25519Util — статическая утилита для работы с подписями Ed25519
|
||||
* на базе Bouncy Castle (bcprov). Совместимо с Java 17.
|
||||
* ---------------------------------------------------------------
|
||||
* Возможности:
|
||||
* • generatePrivateKey() — приватный ключ 32 байта (seed) из SecureRandom.
|
||||
* • generatePrivateKeyFromString(String) — приватный ключ 32 байта из строки через SHA-256.
|
||||
* • derivePublicKey(byte[32]) — публичный ключ 32 байта из приватного.
|
||||
* • sign(byte[], byte[32]) — подпись 64 байта.
|
||||
* • verify(byte[], byte[64], byte[32]) — проверка подписи (true/false).
|
||||
* • keyToBase64(byte[32]) / keyFromBase64(String) — Base64 ⇆ ключ (ровно 32 байта).
|
||||
*.
|
||||
* Форматы:
|
||||
* • Приватный ключ — 32-байтный seed Ed25519.
|
||||
* • Публичный ключ — 32-байтный public key.
|
||||
* • Подпись — 64 байта.
|
||||
*.
|
||||
* Важно:
|
||||
* • Здесь используется «классический» Ed25519 (подпись сырых данных).
|
||||
* Если нужен режим Ed25519ph (prehash), делай отдельный класс.
|
||||
*.
|
||||
* Зависимость (Gradle/Groovy):
|
||||
* implementation 'org.bouncycastle:bcprov-jdk18on:1.78.1'
|
||||
* ===============================================================
|
||||
*/
|
||||
public final class Ed25519Util {
|
||||
|
||||
/** Длина приватного ключа (seed) в байтах. */
|
||||
public static final int PRIVATE_KEY_LEN = 32;
|
||||
/** Длина публичного ключа в байтах. */
|
||||
public static final int PUBLIC_KEY_LEN = 32;
|
||||
/** Длина подписи в байтах. */
|
||||
public static final int SIGNATURE_LEN = 64;
|
||||
|
||||
// Запрещаем инстанцирование: только статические методы
|
||||
private Ed25519Util() {}
|
||||
|
||||
// ===== Надёжный генератор случайных чисел (ленивая инициализация) =====
|
||||
private static final SecureRandom SECURE_RANDOM = createSecureRandom();
|
||||
|
||||
private static SecureRandom createSecureRandom() {
|
||||
try {
|
||||
return SecureRandom.getInstanceStrong();
|
||||
} catch (Exception ignore) {
|
||||
return new SecureRandom();
|
||||
}
|
||||
}
|
||||
|
||||
// =====================================================================
|
||||
// API
|
||||
// =====================================================================
|
||||
|
||||
/**
|
||||
* Сгенерировать приватный ключ (seed) Ed25519: 32 случайных байта.
|
||||
*/
|
||||
public static byte[] generatePrivateKey() {
|
||||
byte[] seed = new byte[PRIVATE_KEY_LEN];
|
||||
SECURE_RANDOM.nextBytes(seed);
|
||||
return seed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Сгенерировать приватный ключ (seed, 32 байта) из произвольной строки:
|
||||
* строка → UTF-8 → SHA-256 → 32 байта.
|
||||
*
|
||||
* @param anyString любая строка (не null)
|
||||
* @return массив 32 байта (seed)
|
||||
*/
|
||||
public static byte[] generatePrivateKeyFromString(String anyString) {
|
||||
Objects.requireNonNull(anyString, "Строка для генерации приватного ключа не должна быть null");
|
||||
byte[] input = anyString.getBytes(StandardCharsets.UTF_8);
|
||||
return HashSHA256Util.sha256(input); // ровно 32 байта
|
||||
}
|
||||
|
||||
/**
|
||||
* Получить публичный ключ (32 байта) из приватного (seed, 32 байта).
|
||||
*/
|
||||
public static byte[] derivePublicKey(byte[] privateKey32) {
|
||||
requireLength(privateKey32, PRIVATE_KEY_LEN, "приватного ключа (seed)");
|
||||
Ed25519PrivateKeyParameters priv = new Ed25519PrivateKeyParameters(privateKey32, 0);
|
||||
Ed25519PublicKeyParameters pub = priv.generatePublicKey();
|
||||
return pub.getEncoded(); // 32 байта
|
||||
}
|
||||
|
||||
/**
|
||||
* Подписать сырые данные (без предварительного хеширования) приватным ключом Ed25519.
|
||||
*
|
||||
* @param data данные для подписи (не null)
|
||||
* @param privateKey32 приватный ключ (seed) 32 байта
|
||||
* @return подпись длиной 64 байта
|
||||
*/
|
||||
public static byte[] sign(byte[] data, byte[] privateKey32) {
|
||||
Objects.requireNonNull(data, "Данные для подписи не должны быть null");
|
||||
requireLength(privateKey32, PRIVATE_KEY_LEN, "приватного ключа (seed)");
|
||||
|
||||
Ed25519PrivateKeyParameters priv = new Ed25519PrivateKeyParameters(privateKey32, 0);
|
||||
Ed25519Signer signer = new Ed25519Signer();
|
||||
signer.init(true, priv);
|
||||
signer.update(data, 0, data.length);
|
||||
byte[] signature = signer.generateSignature();
|
||||
if (signature == null || signature.length != SIGNATURE_LEN) {
|
||||
throw new IllegalStateException("Ожидалась подпись длиной 64 байта.");
|
||||
}
|
||||
return signature;
|
||||
}
|
||||
|
||||
/**
|
||||
* Проверить подпись Ed25519.
|
||||
*
|
||||
* @param data исходные данные
|
||||
* @param signature64 подпись 64 байта
|
||||
* @param publicKey32 публичный ключ 32 байта
|
||||
* @return true, если подпись корректна для этих данных и ключа
|
||||
*/
|
||||
public static boolean verify(byte[] data, byte[] signature64, byte[] publicKey32) {
|
||||
Objects.requireNonNull(data, "Данные для проверки подписи не должны быть null");
|
||||
requireLength(signature64, SIGNATURE_LEN, "подписи Ed25519");
|
||||
requireLength(publicKey32, PUBLIC_KEY_LEN, "публичного ключа");
|
||||
|
||||
Ed25519PublicKeyParameters pub = new Ed25519PublicKeyParameters(publicKey32, 0);
|
||||
Ed25519Signer verifier = new Ed25519Signer();
|
||||
verifier.init(false, pub);
|
||||
verifier.update(data, 0, data.length);
|
||||
return verifier.verifySignature(signature64);
|
||||
}
|
||||
|
||||
/**
|
||||
* Преобразовать 32-байтный ключ (приватный seed или публичный key) в Base64-строку.
|
||||
*/
|
||||
public static String keyToBase64(byte[] key32) {
|
||||
requireLength(key32, 32, "ключа (ожидалось 32 байта)");
|
||||
return Base64.getEncoder().encodeToString(key32);
|
||||
}
|
||||
|
||||
/**
|
||||
* Из Base64-строки получить 32-байтный ключ.
|
||||
* @throws IllegalArgumentException если после декодирования длина ≠ 32
|
||||
*/
|
||||
public static byte[] keyFromBase64(String base64) {
|
||||
Objects.requireNonNull(base64, "Base64-строка не должна быть null");
|
||||
byte[] raw = Base64.getDecoder().decode(base64);
|
||||
requireLength(raw, 32, "ключа после декодирования Base64 (ожидалось 32 байта)");
|
||||
return raw;
|
||||
}
|
||||
|
||||
// =====================================================================
|
||||
// ВСПОМОГАТЕЛЬНЫЕ
|
||||
// =====================================================================
|
||||
|
||||
private static void requireLength(byte[] data, int expectedLen, String what) {
|
||||
if (data == null) {
|
||||
throw new IllegalArgumentException("Массив " + what + " не должен быть null.");
|
||||
}
|
||||
if (data.length != expectedLen) {
|
||||
throw new IllegalArgumentException(
|
||||
"Некорректная длина " + what + ": " + data.length + " байт(а). Ожидалось: " + expectedLen + "."
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package utils.crypto;
|
||||
|
||||
import org.bouncycastle.crypto.digests.SHA256Digest;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
public final class HashSHA256Util {
|
||||
private HashSHA256Util() {}
|
||||
|
||||
/** Посчитать SHA-256 от всего массива. */
|
||||
public static byte[] sha256(byte[] data) {
|
||||
if (data == null) throw new IllegalArgumentException("data == null");
|
||||
SHA256Digest d = new SHA256Digest();
|
||||
d.update(data, 0, data.length);
|
||||
byte[] out = new byte[32];
|
||||
d.doFinal(out, 0);
|
||||
return out;
|
||||
}
|
||||
|
||||
/** Инкрементальный SHA-256 (если нужно будет кормить по кускам). */
|
||||
public static final class Sha256 {
|
||||
private final SHA256Digest d = new SHA256Digest();
|
||||
public Sha256 update(byte[] part) {
|
||||
if (part != null) d.update(part, 0, part.length);
|
||||
return this;
|
||||
}
|
||||
public byte[] doFinal() {
|
||||
byte[] out = new byte[32];
|
||||
d.doFinal(out, 0);
|
||||
return out;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
# utils.crypto
|
||||
|
||||
Пакет отвечает за криптографию — подписи и хэши блоков.
|
||||
Используется при создании и проверке целостности `.bch`-блоков.
|
||||
|
||||
---
|
||||
|
||||
## Классы
|
||||
|
||||
### **Ed25519Util**
|
||||
Работает с подписями Ed25519.
|
||||
Методы:
|
||||
- `generatePrivateKey()` — создать приватный ключ (32 байта)
|
||||
- `generatePrivateKeyFromString(String)` — детерминированный ключ из строки
|
||||
- `derivePublicKey(byte[32])` — получить публичный ключ (32 байта)
|
||||
- `sign(byte[], byte[32])` — подписать данные (64-байтная подпись)
|
||||
- `verify(byte[], byte[64], byte[32])` — проверить подпись
|
||||
|
||||
### **HashUtil**
|
||||
Вычисляет SHA-256.
|
||||
Методы:
|
||||
- `sha256(byte[])` → `[32]` — вернуть хэш массива
|
||||
- `Sha256` — вложенный класс для пошагового хэширования
|
||||
|
||||
### **BchCryptoVerifier**
|
||||
Проверяет подпись и хэш блока перед записью в блокчейн.
|
||||
Методы:
|
||||
- `verifyAll(userLogin, blockchainId, prevHash32, rawBytes, signature64, hash32, publicKey32)`
|
||||
→ `true/false` — корректна ли подпись и хэш
|
||||
- `buildPreimage(...)` — собирает байты, которые подписываются:
|
||||
Reference in New Issue
Block a user