• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Avec112 / commons-security / 18824235666

26 Oct 2025 09:52PM UTC coverage: 90.592% (+0.4%) from 90.164%
18824235666

push

github

Avec112
Add ECC support and refactor key utilities

- Replaced RSA `KeyUtils` with ECC-focused `EciesCipher` supporting ECIES encryption/decryption.
- Expanded `KeyUtils` to include ECC key generation for Ed25519, secp256r1, secp384r1, and secp521r1.
- Updated README with details on ECC features (e.g., ECIES, Ed25519, ECDSA) and usage.
- Enhanced `CryptoUtils` with ECC helpers: ECIES, Ed25519, and ECDSA methods.
- Added comprehensive test coverage for new ECC functionality.

44 of 60 branches covered (73.33%)

Branch coverage included in aggregate %.

553 of 599 relevant lines covered (92.32%)

3.7 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

97.18
src/main/java/com/github/avec112/security/crypto/sign/SignatureUtils.java
1
package com.github.avec112.security.crypto.sign;
2

3
import com.github.avec112.security.crypto.BouncyCastleProviderInitializer;
4
import org.apache.commons.lang3.Validate;
5

6
import java.nio.charset.StandardCharsets;
7
import java.security.GeneralSecurityException;
8
import java.security.PrivateKey;
9
import java.security.PublicKey;
10
import java.security.Signature;
11
import java.security.spec.MGF1ParameterSpec;
12
import java.security.spec.PSSParameterSpec;
13
import java.util.Objects;
14

15
/**
16
 * Unified utility class for creating and verifying digital signatures.
17
 *
18
 * <p>Supports multiple signature algorithms:</p>
19
 * <ul>
20
 *   <li><b>RSASSA-PSS</b> - Modern RSA signature scheme with probabilistic padding</li>
21
 *   <li><b>Ed25519</b> - Modern ECC signature (fastest, deterministic, 64-byte signatures)</li>
22
 *   <li><b>ECDSA</b> - Standards-compliant ECC signature (probabilistic)</li>
23
 * </ul>
24
 *
25
 * <p><b>Example usage:</b></p>
26
 * <pre>{@code
27
 * // RSA signature
28
 * KeyPair rsaKeys = KeyUtils.generateRsaKeyPair();
29
 * byte[] rsaSig = SignatureUtils.sign("data", rsaKeys.getPrivate());
30
 * boolean valid = SignatureUtils.verify(rsaSig, "data", rsaKeys.getPublic());
31
 *
32
 * // Ed25519 signature (recommended for new applications)
33
 * KeyPair ed25519Keys = KeyUtils.generateEd25519KeyPair();
34
 * byte[] ed25519Sig = SignatureUtils.signEd25519("data", ed25519Keys.getPrivate());
35
 * boolean valid = SignatureUtils.verifyEd25519(ed25519Sig, "data", ed25519Keys.getPublic());
36
 *
37
 * // ECDSA signature
38
 * KeyPair ecKeys = KeyUtils.generateSecp256r1KeyPair();
39
 * byte[] ecdsaSig = SignatureUtils.signEcdsa("data", ecKeys.getPrivate());
40
 * boolean valid = SignatureUtils.verifyEcdsa(ecdsaSig, "data", ecKeys.getPublic());
41
 * }</pre>
42
 */
43
public class SignatureUtils extends BouncyCastleProviderInitializer {
44

45
    private static final String RSA_ALGORITHM = "RSASSA-PSS";
46
    private static final String ED25519_ALGORITHM = "Ed25519";
47

48
    // Recommended parameters for SHA-256 PSS
49
    private static final PSSParameterSpec PSS_SHA256_PARAMS = new PSSParameterSpec(
10✔
50
            "SHA-256",
51
            "MGF1",
52
            MGF1ParameterSpec.SHA256,
53
            32,  // salt length in bytes
54
            1    // trailer field (always 1)
55
    );
56

57
    private SignatureUtils() {
58
    }
59

60
    // ========== RSA Signatures (RSASSA-PSS) ==========
61

62
    /**
63
     * Signs the given text using RSASSA-PSS (SHA-256 + MGF1).
64
     *
65
     * @param data       the string to sign
66
     * @param privateKey the RSA private key
67
     * @return the signature bytes
68
     * @throws GeneralSecurityException if signing fails
69
     */
70
    public static byte[] sign(String data, PrivateKey privateKey) throws Exception {
71
        Validate.notBlank(data, "data cannot be blank");
6✔
72
        Objects.requireNonNull(privateKey, "privateKey cannot be null");
4✔
73

74
        return sign(data.getBytes(StandardCharsets.UTF_8), privateKey);
6✔
75
    }
76

77
    /**
78
     * Signs the given byte array using RSASSA-PSS (SHA-256 + MGF1).
79
     *
80
     * @param data       the data to sign
81
     * @param privateKey the RSA private key
82
     * @return the signature bytes
83
     * @throws GeneralSecurityException if signing fails
84
     */
85
    public static byte[] sign(byte[] data, PrivateKey privateKey) throws Exception {
86
        Objects.requireNonNull(data, "data cannot be null");
4✔
87
        Objects.requireNonNull(privateKey, "privateKey cannot be null");
4✔
88

89
        Signature signature = Signature.getInstance(RSA_ALGORITHM);
3✔
90
        signature.setParameter(PSS_SHA256_PARAMS);
3✔
91
        signature.initSign(privateKey);
3✔
92
        signature.update(data);
3✔
93
        return signature.sign();
3✔
94
    }
95

96
    /**
97
     * Verifies a signature for the given text using RSASSA-PSS (SHA-256 + MGF1).
98
     *
99
     * @param signatureBytes the signature to verify
100
     * @param data           the original string
101
     * @param publicKey      the RSA public key
102
     * @return true if the signature is valid, false otherwise
103
     * @throws GeneralSecurityException if verification fails
104
     */
105
    public static boolean verify(byte[] signatureBytes, String data, PublicKey publicKey) throws Exception {
106
        Objects.requireNonNull(signatureBytes);
3✔
107
        Validate.notBlank(data);
3✔
108
        Objects.requireNonNull(publicKey);
3✔
109

110
        return verify(signatureBytes, data.getBytes(StandardCharsets.UTF_8), publicKey);
7✔
111
    }
112

113
    /**
114
     * Verifies a signature for the given byte array using RSASSA-PSS (SHA-256 + MGF1).
115
     *
116
     * @param signatureBytes the signature to verify
117
     * @param data           the original data
118
     * @param publicKey      the RSA public key
119
     * @return true if the signature is valid, false otherwise
120
     * @throws GeneralSecurityException if verification fails
121
     */
122
    public static boolean verify(byte[] signatureBytes, byte[] data, PublicKey publicKey) throws Exception {
123
        Objects.requireNonNull(signatureBytes);
3✔
124
        Objects.requireNonNull(data);
3✔
125
        Objects.requireNonNull(publicKey);
3✔
126

127
        Signature signature = Signature.getInstance(RSA_ALGORITHM);
3✔
128
        signature.setParameter(PSS_SHA256_PARAMS);
3✔
129
        signature.initVerify(publicKey);
3✔
130
        signature.update(data);
3✔
131
        return signature.verify(signatureBytes);
4✔
132
    }
133

134
    // ========== Ed25519 Signatures (ECC-based) ==========
135

136
    /**
137
     * Signs the given text using Ed25519.
138
     * Ed25519 provides fast, deterministic signatures with 128-bit security (equivalent to RSA-3072).
139
     *
140
     * @param data       the string to sign
141
     * @param privateKey the Ed25519 private key
142
     * @return the signature bytes (64 bytes)
143
     * @throws GeneralSecurityException if signing fails
144
     * @throws IllegalArgumentException if data is blank or privateKey is null
145
     */
146
    public static byte[] signEd25519(String data, PrivateKey privateKey) throws Exception {
147
        Validate.notBlank(data, "data cannot be blank");
6✔
148
        Objects.requireNonNull(privateKey, "privateKey cannot be null");
4✔
149

150
        return signEd25519(data.getBytes(StandardCharsets.UTF_8), privateKey);
6✔
151
    }
152

153
    /**
154
     * Signs the given byte array using Ed25519.
155
     *
156
     * @param data       the data to sign
157
     * @param privateKey the Ed25519 private key
158
     * @return the signature bytes (64 bytes)
159
     * @throws GeneralSecurityException if signing fails
160
     * @throws IllegalArgumentException if data or privateKey is null
161
     */
162
    public static byte[] signEd25519(byte[] data, PrivateKey privateKey) throws Exception {
163
        Objects.requireNonNull(data, "data cannot be null");
4✔
164
        Objects.requireNonNull(privateKey, "privateKey cannot be null");
4✔
165

166
        Signature signature = Signature.getInstance(ED25519_ALGORITHM);
3✔
167
        signature.initSign(privateKey);
3✔
168
        signature.update(data);
3✔
169
        return signature.sign();
3✔
170
    }
171

172
    /**
173
     * Verifies an Ed25519 signature for the given text.
174
     *
175
     * @param signatureBytes the signature to verify
176
     * @param data           the original string
177
     * @param publicKey      the Ed25519 public key
178
     * @return true if the signature is valid, false otherwise
179
     * @throws GeneralSecurityException if verification fails
180
     * @throws IllegalArgumentException if any parameter is null or data is blank
181
     */
182
    public static boolean verifyEd25519(byte[] signatureBytes, String data, PublicKey publicKey) throws Exception {
183
        Objects.requireNonNull(signatureBytes, "signatureBytes cannot be null");
4✔
184
        Validate.notBlank(data, "data cannot be blank");
6✔
185
        Objects.requireNonNull(publicKey, "publicKey cannot be null");
4✔
186

187
        return verifyEd25519(signatureBytes, data.getBytes(StandardCharsets.UTF_8), publicKey);
7✔
188
    }
189

190
    /**
191
     * Verifies an Ed25519 signature for the given byte array.
192
     *
193
     * @param signatureBytes the signature to verify
194
     * @param data           the original data
195
     * @param publicKey      the Ed25519 public key
196
     * @return true if the signature is valid, false otherwise
197
     * @throws GeneralSecurityException if verification fails
198
     * @throws IllegalArgumentException if any parameter is null
199
     */
200
    public static boolean verifyEd25519(byte[] signatureBytes, byte[] data, PublicKey publicKey) throws Exception {
201
        Objects.requireNonNull(signatureBytes, "signatureBytes cannot be null");
4✔
202
        Objects.requireNonNull(data, "data cannot be null");
4✔
203
        Objects.requireNonNull(publicKey, "publicKey cannot be null");
4✔
204

205
        Signature signature = Signature.getInstance(ED25519_ALGORITHM);
3✔
206
        signature.initVerify(publicKey);
3✔
207
        signature.update(data);
3✔
208
        return signature.verify(signatureBytes);
4✔
209
    }
210

211
    // ========== ECDSA Signatures (ECC-based) ==========
212

213
    /**
214
     * Signs the given text using ECDSA (Elliptic Curve Digital Signature Algorithm).
215
     * The hash algorithm is automatically selected based on the key's curve.
216
     *
217
     * @param data       the string to sign
218
     * @param privateKey the ECDSA private key (secp256r1, secp384r1, or secp521r1)
219
     * @return the signature bytes
220
     * @throws GeneralSecurityException if signing fails
221
     * @throws IllegalArgumentException if data is blank or privateKey is null
222
     */
223
    public static byte[] signEcdsa(String data, PrivateKey privateKey) throws Exception {
224
        Validate.notBlank(data, "data cannot be blank");
6✔
225
        Objects.requireNonNull(privateKey, "privateKey cannot be null");
4✔
226

227
        return signEcdsa(data.getBytes(StandardCharsets.UTF_8), privateKey);
6✔
228
    }
229

230
    /**
231
     * Signs the given byte array using ECDSA with the appropriate hash algorithm for the key.
232
     *
233
     * @param data       the data to sign
234
     * @param privateKey the ECDSA private key
235
     * @return the signature bytes
236
     * @throws GeneralSecurityException if signing fails
237
     * @throws IllegalArgumentException if data or privateKey is null
238
     */
239
    public static byte[] signEcdsa(byte[] data, PrivateKey privateKey) throws Exception {
240
        Objects.requireNonNull(data, "data cannot be null");
4✔
241
        Objects.requireNonNull(privateKey, "privateKey cannot be null");
4✔
242

243
        String algorithm = determineEcdsaAlgorithm(privateKey);
3✔
244
        Signature signature = Signature.getInstance(algorithm);
3✔
245
        signature.initSign(privateKey);
3✔
246
        signature.update(data);
3✔
247
        return signature.sign();
3✔
248
    }
249

250
    /**
251
     * Verifies an ECDSA signature for the given text.
252
     *
253
     * @param signatureBytes the signature to verify
254
     * @param data           the original string
255
     * @param publicKey      the ECDSA public key
256
     * @return true if the signature is valid, false otherwise
257
     * @throws GeneralSecurityException if verification fails
258
     * @throws IllegalArgumentException if any parameter is null or data is blank
259
     */
260
    public static boolean verifyEcdsa(byte[] signatureBytes, String data, PublicKey publicKey) throws Exception {
261
        Objects.requireNonNull(signatureBytes, "signatureBytes cannot be null");
4✔
262
        Validate.notBlank(data, "data cannot be blank");
6✔
263
        Objects.requireNonNull(publicKey, "publicKey cannot be null");
4✔
264

265
        return verifyEcdsa(signatureBytes, data.getBytes(StandardCharsets.UTF_8), publicKey);
7✔
266
    }
267

268
    /**
269
     * Verifies an ECDSA signature for the given byte array.
270
     *
271
     * @param signatureBytes the signature to verify
272
     * @param data           the original data
273
     * @param publicKey      the ECDSA public key
274
     * @return true if the signature is valid, false otherwise
275
     * @throws GeneralSecurityException if verification fails
276
     * @throws IllegalArgumentException if any parameter is null
277
     */
278
    public static boolean verifyEcdsa(byte[] signatureBytes, byte[] data, PublicKey publicKey) throws Exception {
279
        Objects.requireNonNull(signatureBytes, "signatureBytes cannot be null");
4✔
280
        Objects.requireNonNull(data, "data cannot be null");
4✔
281
        Objects.requireNonNull(publicKey, "publicKey cannot be null");
4✔
282

283
        String algorithm = determineEcdsaAlgorithm(publicKey);
3✔
284
        Signature signature = Signature.getInstance(algorithm);
3✔
285
        signature.initVerify(publicKey);
3✔
286
        signature.update(data);
3✔
287
        return signature.verify(signatureBytes);
4✔
288
    }
289

290
    /**
291
     * Determines the appropriate ECDSA signature algorithm based on the key.
292
     * Defaults to SHA256withECDSA for EC keys.
293
     *
294
     * @param key the EC key (public or private)
295
     * @return the signature algorithm name (e.g., "SHA256withECDSA")
296
     */
297
    private static String determineEcdsaAlgorithm(java.security.Key key) {
298
        String algorithm = key.getAlgorithm();
3✔
299
        if (!"EC".equals(algorithm)) {
4!
300
            throw new IllegalArgumentException("Key must be an EC key, but was: " + algorithm);
×
301
        }
302
        // Default to SHA256withECDSA
303
        // In production, you could inspect the key's ECParameterSpec to determine exact curve
304
        return "SHA256withECDSA";
2✔
305
    }
306
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc