Skip to content

Commit 1c60614

Browse files
committed
Release v2.0 - Java 25 upgrade with new cryptographic features
BREAKING CHANGES: - Upgraded to Java 25 (from Java 21) - Replaced ru.rutoken:pkcs11jna with custom JNA bindings NEW FEATURES: - PKCS11ECDSASigner: ECDSA signing with SHA-1/224/256/384/512 - PKCS11Digest: Hardware hashing (MD5, SHA-1/224/256/384/512, RIPEMD-160) - PKCS11Random: Hardware RNG extending SecureRandom - PKCS11KeyDerivation: ECDH key derivation with multiple KDFs - Custom JNA Cryptoki interface based on OASIS PKCS#11 v2.40 - Multi-part signing for large files (>16KB) IMPROVEMENTS: - PKCS11Signer: Added SHA-1/224/384/512 algorithm support - PKCS11RSACrypto: Simplified to PKCS#1 v1.5 only - Added comprehensive exception types for new operations - Updated all dependencies to latest versions DEPENDENCIES: - JNA 5.16.0, dss-token 6.3, BouncyCastle 1.80 - Lombok 1.18.42 (Java 25 support) - JUnit 5.11.4, Mockito 5.15.2 DOCUMENTATION: - Updated README with all new features and examples - Added OpenSC 0.26.1 to tested environments - Updated architecture diagrams Lub krótsza wersja: Release v2.0 - Major update with Java 25 and new crypto features - Upgrade to Java 25 with updated dependencies - Add ECDSA signing, hardware RNG, digest, and ECDH key derivation - Replace external PKCS#11 bindings with custom JNA implementation - Add multi-part signing for large files - Update README with OpenSC 0.26.1 support
1 parent b37b376 commit 1c60614

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

48 files changed

+5766
-268
lines changed

src/main/java/pl/mlodawski/security/example/PKCS11Example.java

Lines changed: 106 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package pl.mlodawski.security.example;
22

3+
import com.sun.jna.NativeLong;
34
import pl.mlodawski.security.pkcs11.*;
45
import pl.mlodawski.security.pkcs11.model.*;
56

@@ -91,9 +92,15 @@ public void onDeviceError(PKCS11Device device, Exception error) {
9192
listSupportedAlgorithms(manager, session);
9293
break;
9394
case 8:
95+
generateHardwareRandom(manager, session);
96+
break;
97+
case 9:
98+
computeHardwareDigest(manager, session);
99+
break;
100+
case 10:
94101
selectedDevice = null;
95102
return;
96-
case 9:
103+
case 11:
97104
session.close();
98105
selectedDevice = null;
99106
PIN = null;
@@ -104,10 +111,6 @@ public void onDeviceError(PKCS11Device device, Exception error) {
104111
default:
105112
System.out.println("Invalid choice. Please try again.");
106113
}
107-
108-
// if (choice == 6) {
109-
// break;
110-
// }
111114
}
112115
} catch (Exception e) {
113116
System.out.println("Session error: " + e.getMessage());
@@ -145,14 +148,12 @@ private void signFile(PKCS11Manager manager, PKCS11Session session) {
145148
selectedPair.getKeyHandle(),
146149
fileContent);
147150

148-
// Save signature to file
149151
String signatureFilePath = filePath + ".sig";
150152
Files.write(Paths.get(signatureFilePath), signature);
151153

152154
System.out.println("File signed successfully. Signature saved to: " + signatureFilePath);
153155
System.out.println("Signature (Base64): " + Base64.getEncoder().encodeToString(signature));
154156

155-
// Verify signature immediately
156157
boolean isSignatureValid = signer.verifySignature(fileContent, signature, selectedPair.getCertificate());
157158
System.out.println("Signature verification: " + (isSignatureValid ? "Valid" : "Invalid"));
158159
} catch (Exception e) {
@@ -208,11 +209,9 @@ private void encryptDecryptFile(PKCS11Manager manager, PKCS11Session session) {
208209

209210
byte[] fileContent = Files.readAllBytes(Paths.get(filePath));
210211

211-
// Encrypt file using hybrid encryption
212212
PKCS11Crypto crypto = new PKCS11Crypto();
213213
byte[][] encryptedPackage = crypto.encryptData(fileContent, selectedPair.getCertificate());
214214

215-
// Save encrypted components
216215
String encryptedKeyPath = filePath + ".key.enc";
217216
String encryptedIVPath = filePath + ".iv";
218217
String encryptedDataPath = filePath + ".data.enc";
@@ -232,7 +231,6 @@ private void encryptDecryptFile(PKCS11Manager manager, PKCS11Session session) {
232231
return;
233232
}
234233

235-
// Decrypt file
236234
byte[][] decryptPackage = new byte[][]{
237235
Files.readAllBytes(Paths.get(encryptedKeyPath)),
238236
Files.readAllBytes(Paths.get(encryptedIVPath)),
@@ -246,12 +244,10 @@ private void encryptDecryptFile(PKCS11Manager manager, PKCS11Session session) {
246244
decryptPackage
247245
);
248246

249-
// Save decrypted file
250247
String decryptedFilePath = filePath + ".dec";
251248
Files.write(Paths.get(decryptedFilePath), decryptedData);
252249
System.out.println("File decrypted successfully. Saved to: " + decryptedFilePath);
253250

254-
// Calculate and display checksums
255251
String originalChecksum = getFileChecksum(fileContent);
256252
String decryptedChecksum = getFileChecksum(decryptedData);
257253

@@ -379,15 +375,20 @@ private boolean getPINFromUser() {
379375
private void displayMenu() {
380376
System.out.println("\n--- PKCS#11 Operations Menu ---");
381377
System.out.println("Current device: " + selectedDevice.getLabel());
378+
System.out.println("--- Basic Operations ---");
382379
System.out.println("1. List Available Certificates");
383-
System.out.println("2. Sign a Message");
380+
System.out.println("2. Sign a Message (RSA-PKCS)");
384381
System.out.println("3. Sign a File");
385382
System.out.println("4. Verify File Signature");
386-
System.out.println("5. Encrypt and Decrypt Data");
387-
System.out.println("6. Encrypt and Decrypt File");
383+
System.out.println("5. Encrypt and Decrypt Data (Hybrid)");
384+
System.out.println("6. Encrypt and Decrypt File (Hybrid)");
388385
System.out.println("7. List Supported Algorithms");
389-
System.out.println("8. Exit");
390-
System.out.println("9. Change Device");
386+
System.out.println("--- Advanced Operations ---");
387+
System.out.println("8. Generate Hardware Random Numbers");
388+
System.out.println("9. Compute Hardware Digest (Hash)");
389+
System.out.println("--- System ---");
390+
System.out.println("10. Exit");
391+
System.out.println("11. Change Device");
391392
System.out.print("Enter your choice: ");
392393
}
393394

@@ -457,12 +458,10 @@ private void encryptDecryptData(PKCS11Manager manager, PKCS11Session session) {
457458

458459
PKCS11Crypto crypto = new PKCS11Crypto();
459460

460-
// Encrypt data
461461
byte[][] encryptedPackage = crypto.encryptData(dataToEncrypt.getBytes(), selectedPair.getCertificate());
462462
System.out.println("Data encrypted successfully.");
463463
System.out.println("Encrypted data (Base64): " + Base64.getEncoder().encodeToString(encryptedPackage[2]));
464464

465-
// Decrypt data
466465
byte[] decryptedData = crypto.decryptData(
467466
manager.getPkcs11(),
468467
session.getSession(),
@@ -504,6 +503,94 @@ private void listSupportedAlgorithms(PKCS11Manager manager, PKCS11Session sessio
504503
System.out.println("Error listing algorithms: " + e.getMessage());
505504
}
506505
}
506+
507+
private void generateHardwareRandom(PKCS11Manager manager, PKCS11Session session) {
508+
try {
509+
Scanner scanner = new Scanner(System.in);
510+
System.out.print("Enter number of random bytes to generate (1-1024): ");
511+
int numBytes = scanner.nextInt();
512+
513+
if (numBytes < 1 || numBytes > 1024) {
514+
System.out.println("Invalid number of bytes. Please enter a value between 1 and 1024.");
515+
return;
516+
}
517+
518+
PKCS11Random random = new PKCS11Random(manager.getPkcs11(), session.getSession());
519+
byte[] randomBytes = random.generateRandomBytes(numBytes);
520+
521+
System.out.println("\nHardware-generated random bytes:");
522+
System.out.println("Hex: " + bytesToHex(randomBytes));
523+
System.out.println("Base64: " + Base64.getEncoder().encodeToString(randomBytes));
524+
System.out.println("Generated " + randomBytes.length + " random bytes from hardware token.");
525+
} catch (Exception e) {
526+
System.out.println("Error generating random numbers: " + e.getMessage());
527+
}
528+
}
529+
530+
private void computeHardwareDigest(PKCS11Manager manager, PKCS11Session session) {
531+
try {
532+
Scanner scanner = new Scanner(System.in);
533+
534+
System.out.println("\nSelect hash algorithm:");
535+
PKCS11Digest.Algorithm[] algorithms = PKCS11Digest.Algorithm.values();
536+
for (int i = 0; i < algorithms.length; i++) {
537+
System.out.printf("%d. %s (%d bytes)%n", i + 1, algorithms[i].name(), algorithms[i].getDigestLength());
538+
}
539+
System.out.print("Enter choice: ");
540+
int algoChoice = scanner.nextInt();
541+
scanner.nextLine(); // consume newline
542+
543+
if (algoChoice < 1 || algoChoice > algorithms.length) {
544+
System.out.println("Invalid algorithm choice.");
545+
return;
546+
}
547+
PKCS11Digest.Algorithm selectedAlgorithm = algorithms[algoChoice - 1];
548+
549+
System.out.print("Enter data to hash (or 'file:path' to hash a file): ");
550+
String input = scanner.nextLine();
551+
552+
byte[] dataToHash;
553+
if (input.startsWith("file:")) {
554+
String filePath = input.substring(5);
555+
if (!Files.exists(Paths.get(filePath))) {
556+
System.out.println("File does not exist: " + filePath);
557+
return;
558+
}
559+
dataToHash = Files.readAllBytes(Paths.get(filePath));
560+
System.out.println("Hashing file: " + filePath + " (" + dataToHash.length + " bytes)");
561+
} else {
562+
dataToHash = input.getBytes();
563+
}
564+
565+
PKCS11Digest digest = new PKCS11Digest(manager.getPkcs11(), session.getSession());
566+
byte[] hashResult = digest.digest(selectedAlgorithm, dataToHash);
567+
568+
System.out.println("\nHardware-computed " + selectedAlgorithm.name() + " digest:");
569+
System.out.println("Hex: " + bytesToHex(hashResult));
570+
System.out.println("Base64: " + Base64.getEncoder().encodeToString(hashResult));
571+
572+
try {
573+
String javaAlgoName = selectedAlgorithm.name().replace("_", "-");
574+
if (javaAlgoName.equals("SHA1")) javaAlgoName = "SHA-1";
575+
MessageDigest md = MessageDigest.getInstance(javaAlgoName);
576+
byte[] softwareHash = md.digest(dataToHash);
577+
boolean matches = Arrays.equals(hashResult, softwareHash);
578+
System.out.println("Matches software hash: " + (matches ? "Yes" : "No"));
579+
} catch (NoSuchAlgorithmException e) {
580+
System.out.println("(Software comparison not available for this algorithm)");
581+
}
582+
} catch (Exception e) {
583+
System.out.println("Error computing digest: " + e.getMessage());
584+
}
585+
}
586+
587+
private String bytesToHex(byte[] bytes) {
588+
StringBuilder sb = new StringBuilder();
589+
for (byte b : bytes) {
590+
sb.append(String.format("%02x", b));
591+
}
592+
return sb.toString();
593+
}
507594
}
508595

509596

src/main/java/pl/mlodawski/security/pkcs11/PKCS11Crypto.java

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,13 @@
22

33
import lombok.extern.slf4j.Slf4j;
44
import org.bouncycastle.jce.provider.BouncyCastleProvider;
5-
import pl.mlodawski.security.pkcs11.exceptions.CryptoInitializationException;
65
import pl.mlodawski.security.pkcs11.exceptions.DecryptionException;
76
import pl.mlodawski.security.pkcs11.exceptions.EncryptionException;
87
import pl.mlodawski.security.pkcs11.exceptions.InvalidInputException;
9-
import ru.rutoken.pkcs11jna.CK_MECHANISM;
10-
import ru.rutoken.pkcs11jna.Pkcs11;
11-
import ru.rutoken.pkcs11jna.Pkcs11Constants;
8+
import pl.mlodawski.security.pkcs11.jna.Cryptoki;
9+
import pl.mlodawski.security.pkcs11.jna.constants.MechanismType;
10+
import pl.mlodawski.security.pkcs11.jna.constants.ReturnValue;
11+
import pl.mlodawski.security.pkcs11.jna.structure.Mechanism;
1212
import com.sun.jna.NativeLong;
1313
import com.sun.jna.ptr.NativeLongByReference;
1414

@@ -77,16 +77,15 @@ public byte[][] encryptData(byte[] dataToEncrypt, X509Certificate certificate) {
7777
* @return the decrypted data as a byte array.
7878
* @throws DecryptionException if the decryption process fails.
7979
*/
80-
public byte[] decryptData(Pkcs11 pkcs11, NativeLong session, NativeLong privateKeyHandle, byte[][] encryptedPackage) {
80+
public byte[] decryptData(Cryptoki pkcs11, NativeLong session, NativeLong privateKeyHandle, byte[][] encryptedPackage) {
8181
validateDecryptInput(pkcs11, session, privateKeyHandle, encryptedPackage);
8282

8383
byte[] encryptedKey = encryptedPackage[0];
8484
byte[] iv = encryptedPackage[1];
8585
byte[] encryptedData = encryptedPackage[2];
8686

8787
try {
88-
CK_MECHANISM mechanism = new CK_MECHANISM();
89-
mechanism.mechanism = new NativeLong(Pkcs11Constants.CKM_RSA_PKCS);
88+
Mechanism mechanism = new Mechanism(MechanismType.RSA_PKCS);
9089
NativeLong rv = pkcs11.C_DecryptInit(session, mechanism, privateKeyHandle);
9190
checkResult(rv, "Failed to initialize decryption");
9291

@@ -111,7 +110,7 @@ public byte[] decryptData(Pkcs11 pkcs11, NativeLong session, NativeLong privateK
111110
* @return the decrypted data as a byte array
112111
* @throws DecryptionException if the decryption process fails
113112
*/
114-
private byte[] decrypt(Pkcs11 pkcs11, NativeLong session, byte[] encryptedData) {
113+
private byte[] decrypt(Cryptoki pkcs11, NativeLong session, byte[] encryptedData) {
115114
try {
116115
NativeLongByReference decryptedDataLen = new NativeLongByReference();
117116
NativeLong result = pkcs11.C_Decrypt(session, encryptedData, new NativeLong(encryptedData.length), null, decryptedDataLen);
@@ -136,7 +135,7 @@ private byte[] decrypt(Pkcs11 pkcs11, NativeLong session, byte[] encryptedData)
136135
* @throws DecryptionException if the result is not CKR_OK.
137136
*/
138137
private void checkResult(NativeLong result, String errorMessage) {
139-
if (!result.equals(new NativeLong(Pkcs11Constants.CKR_OK))) {
138+
if (!ReturnValue.isSuccess(result)) {
140139
throw new DecryptionException(errorMessage + ": " + result, null);
141140
}
142141
}
@@ -166,7 +165,7 @@ private void validateEncryptInput(byte[] dataToEncrypt, X509Certificate certific
166165
* @param encryptedPackage the encrypted data package, must be an array of three non-null byte arrays
167166
* @throws InvalidInputException if any of the input parameters are invalid
168167
*/
169-
private void validateDecryptInput(Pkcs11 pkcs11, NativeLong session, NativeLong privateKeyHandle, byte[][] encryptedPackage) {
168+
private void validateDecryptInput(Cryptoki pkcs11, NativeLong session, NativeLong privateKeyHandle, byte[][] encryptedPackage) {
170169
if (pkcs11 == null) {
171170
throw new InvalidInputException("pkcs11 instance cannot be null");
172171
}

0 commit comments

Comments
 (0)