Search code examples
javabouncycastle

"Scalar is not in the interval [1, n - 1]" exception when signing ECDSA with SHA-384


I want to produce 384-bit elliptical curve signature with SHA-384, to generate the keys I run the steps below by looking at this SO Generate EC KeyPair from OpenSSL command line

openssl ecparam -name secp384r1 -genkey -noout -out d:/private.ec.key
openssl ec -in d:/private.pem -pubout -out d:/public.pem
openssl pkcs8 -topk8 -nocrypt -in d:/private.ec.key -out d:/private.pem

I pressed enter when I was asked for password (as I do not want to have password protected keys).

Then I downloaded bcprov-jdk15on-168.jar Bouncy Castle lib, for my JRE 14 and added it to my class path. And made small test application, copied the content of these keys directly into the java source code to simply things.

Made a simple stripping method to remove the 'text' part of the key and decode its base64 content:

  private static byte[] bytesFromKeyStrings(String string) {
    string = string.replaceAll("-----BEGIN PRIVATE KEY-----", "");
    string = string.replaceAll("-----BEGIN EC PRIVATE KEY-----", "");   
    string = string.replaceAll("-----END EC PRIVATE KEY-----", "");
    string = string.replaceAll("-----END PRIVATE KEY-----", "");    
    string = string.replaceAll("\r", "");
    string = string.replaceAll("\n", "");
    var bytes = Base64.getDecoder().decode(string);
    return bytes;
  }  

Then from these bytes I produce a PrivateKey:

  private static PrivateKey keyFromBytes(byte[] pkcs8key) throws NoSuchAlgorithmException, InvalidKeySpecException {
    Security.addProvider(BOUNCY_CASTLE_PROVIDER);
    var spec = ECNamedCurveTable.getParameterSpec("secp384r1");;
    var ecPrivateKeySpec = new ECPrivateKeySpec(new BigInteger(1, pkcs8key), spec);
      
    var factory = KeyFactory.getInstance("ECDSA", BOUNCY_CASTLE_PROVIDER);
    return factory.generatePrivate(ecPrivateKeySpec);
  }

But when I'm going to use the PrivateKey to sign the message (in this case array of zeros) then I get the exception:

  var key = keyFromBytes(bytesFromKeyStrings(privatePem));
  var ecdsaSign = Signature.getInstance("SHA384withECDSA", BOUNCY_CASTLE_PROVIDER);
  ecdsaSign.initSign(key, new SecureRandom());
  ecdsaSign.update(content);
  var signature = ecdsaSign.sign();

Caused on the line:

ecdsaSign.initSign(key, new SecureRandom());

java.lang.IllegalArgumentException: Scalar is not in the interval [1, n - 1]
    at org.bouncycastle.provider/org.bouncycastle.crypto.params.ECDomainParameters.validatePrivateScalar(ECDomainParameters.java:146)
    at org.bouncycastle.provider/org.bouncycastle.crypto.params.ECPrivateKeyParameters.<init>(ECPrivateKeyParameters.java:16)
    at org.bouncycastle.provider/org.bouncycastle.jcajce.provider.asymmetric.util.ECUtil.generatePrivateKeyParameter(ECUtil.java:245)
    at org.bouncycastle.provider/org.bouncycastle.jcajce.provider.asymmetric.ec.SignatureSpi.engineInitSign(Unknown Source)
    at java.base/java.security.SignatureSpi.engineInitSign(SignatureSpi.java:131)
    at java.base/java.security.Signature$Delegate.engineInitSign(Signature.java:1364)
    at java.base/java.security.Signature.initSign(Signature.java:658)
    at ecdsa/ecdsa.Main.main(Main.java:75)

If I understand ECDSA correctly that we need to multiply the private key by some random value, so I'm providing it secureRandom. What is strange that my previous attempt when I was not trying to read the keys and generated them them on the fly with KeyPairGenerator.getInstance("EC") (without bouncycastle library) then these generated keys can be used to sign the message without a exception and I verified it separately that produced signature was correct.

Do I produce the keys badly, or is there some other way to provide the multiplier constant so then it is not trying to sign directly with the private key and giving me the exception?

Or am I missing something silly?

Below is full listing of my test application which can reproduce the exception:

package ecdsa;

import java.math.BigInteger;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.SecureRandom;
import java.security.Security;
import java.security.Signature;
import java.security.spec.InvalidKeySpecException;
import java.util.Base64;

import org.bouncycastle.jce.ECNamedCurveTable;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.jce.spec.ECPrivateKeySpec;

public class Main {
   
  private final static String privatePem = "-----BEGIN PRIVATE KEY-----\r\n"
      + "MIG2AgEAMBAGByqGSM49AgEGBSuBBAAiBIGeMIGbAgEBBDBBdsy5smSA2+DvnIdx\r\n"
      + "bqq6GwwHadEdtHqcKO9N8hB0/NKvFOCmHBSfBpYPgCWlybGhZANiAAQ0iyw2wPjs\r\n"
      + "zhale0mPkiCCTzcNzTW1g7zqUoN3XNuZbaK1FHhVVMsywUaS6MSUJI6r1QcUziMm\r\n"
      + "MWGYZUSSq4noniTDt8INj/ElndKhbh3oSCq3j1oKK9cYXi3uGuDFXks=\r\n"
      + "-----END PRIVATE KEY-----\r\n";  
  
  private final static String privateEc = "-----BEGIN EC PRIVATE KEY-----\r\n"
      + "MIGkAgEBBDBBdsy5smSA2+DvnIdxbqq6GwwHadEdtHqcKO9N8hB0/NKvFOCmHBSf\r\n"
      + "BpYPgCWlybGgBwYFK4EEACKhZANiAAQ0iyw2wPjszhale0mPkiCCTzcNzTW1g7zq\r\n"
      + "UoN3XNuZbaK1FHhVVMsywUaS6MSUJI6r1QcUziMmMWGYZUSSq4noniTDt8INj/El\r\n"
      + "ndKhbh3oSCq3j1oKK9cYXi3uGuDFXks=\r\n"
      + "-----END EC PRIVATE KEY-----\r\n";
  
  private static final BouncyCastleProvider BOUNCY_CASTLE_PROVIDER = new BouncyCastleProvider();
  
  private static byte[] bytesFromKeyStrings(String string) {
    string = string.replaceAll("-----BEGIN PRIVATE KEY-----", "");
    string = string.replaceAll("-----BEGIN EC PRIVATE KEY-----", "");   
    string = string.replaceAll("-----END EC PRIVATE KEY-----", "");
    string = string.replaceAll("-----END PRIVATE KEY-----", "");    
    string = string.replaceAll("\r", "");
    string = string.replaceAll("\n", "");
    var bytes = Base64.getDecoder().decode(string);
    return bytes;
  }  
  
  
  private static PrivateKey keyFromBytes(byte[] pkcs8key) throws NoSuchAlgorithmException, InvalidKeySpecException {
    Security.addProvider(BOUNCY_CASTLE_PROVIDER);
    var spec = ECNamedCurveTable.getParameterSpec("secp384r1");;
    var ecPrivateKeySpec = new ECPrivateKeySpec(new BigInteger(1, pkcs8key), spec);
      
    var factory = KeyFactory.getInstance("ECDSA", BOUNCY_CASTLE_PROVIDER);
    return factory.generatePrivate(ecPrivateKeySpec);
  }
  
  
  public static void main(String[] args) {
    var content = new byte[100];
    
    try {
      var key = keyFromBytes(bytesFromKeyStrings(privatePem));
      var ecdsaSign = Signature.getInstance("SHA384withECDSA", BOUNCY_CASTLE_PROVIDER);
      ecdsaSign.initSign(key, new SecureRandom());
      ecdsaSign.update(content);
      
      var signature = ecdsaSign.sign();
    } catch (Exception e) {
      e.printStackTrace();
    }     
  }

}

Edit: Updated the example (previously I was using encrypted private key)


Solution

  • I'm answering in a second answer as my first answer does not fit to the question any longer because you edited your question and changed the private key pem to unencrypted data.

    As your key is the encoded form of a private key it has a PKCS#8 structure, so any reader is been able to identify the kind of key and the underlying curve so you don't need to build it with ECPrivateKeySpec. There is a more simple way using this code:

    KeyFactory keyFactory = KeyFactory.getInstance("EC", "BC");
    PKCS8EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(pkcs8key);
    return keyFactory.generatePrivate(privateKeySpec);
    

    Same is for the public key, just use the X509EncodedKeySpec (see full code example).

    I appended your bytesFromKeyStrings-function to convert the key data for public keys as well into a byte array.

    Below is a full sample code that read the private key pem, signs the message, read the public key pem (derived from the private key) and verify the signature with a "true" as expected result.

    output:

    signatureVerified: true
    

    Security warning: the following code has no exception handling and is for educational purpose only:

    import org.bouncycastle.jce.provider.BouncyCastleProvider;
    
    import java.security.*;
    import java.security.spec.InvalidKeySpecException;
    import java.security.spec.PKCS8EncodedKeySpec;
    import java.security.spec.X509EncodedKeySpec;
    import java.util.Base64;
    
    public class MainSo {
    
        private final static String privatePem = "-----BEGIN PRIVATE KEY-----\r\n"
                + "MIG2AgEAMBAGByqGSM49AgEGBSuBBAAiBIGeMIGbAgEBBDBBdsy5smSA2+DvnIdx\r\n"
                + "bqq6GwwHadEdtHqcKO9N8hB0/NKvFOCmHBSfBpYPgCWlybGhZANiAAQ0iyw2wPjs\r\n"
                + "zhale0mPkiCCTzcNzTW1g7zqUoN3XNuZbaK1FHhVVMsywUaS6MSUJI6r1QcUziMm\r\n"
                + "MWGYZUSSq4noniTDt8INj/ElndKhbh3oSCq3j1oKK9cYXi3uGuDFXks=\r\n"
                + "-----END PRIVATE KEY-----\r\n";
    /*
    Key Details:
       Type: EC
       Size (bits): 384
       Curve Name: secp384r1
       Curve OID: 1.3.132.0.34
       Public key (x):
    348b2c36c0f8ecce16a57b498f9220824f370dcd35b583bcea5283775cdb996d
    a2b514785554cb32c14692e8c494248e
       Public key (y):
    abd50714ce2326316198654492ab89e89e24c3b7c20d8ff1259dd2a16e1de848
    2ab78f5a0a2bd7185e2dee1ae0c55e4b
       Private key (d):
    4176ccb9b26480dbe0ef9c87716eaaba1b0c0769d11db47a9c28ef4df21074fc
    d2af14e0a61c149f06960f8025a5c9b1
    
    Public Key in PEM Format:
    -----BEGIN PUBLIC KEY-----
    MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAENIssNsD47M4WpXtJj5Iggk83Dc01tYO8
    6lKDd1zbmW2itRR4VVTLMsFGkujElCSOq9UHFM4jJjFhmGVEkquJ6J4kw7fCDY/x
    JZ3SoW4d6Egqt49aCivXGF4t7hrgxV5L
    -----END PUBLIC KEY-----
     */
    
        private final static String publicPem = "-----BEGIN PUBLIC KEY-----\n" +
                "MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAENIssNsD47M4WpXtJj5Iggk83Dc01tYO8\n" +
                "6lKDd1zbmW2itRR4VVTLMsFGkujElCSOq9UHFM4jJjFhmGVEkquJ6J4kw7fCDY/x\n" +
                "JZ3SoW4d6Egqt49aCivXGF4t7hrgxV5L\n" +
                "-----END PUBLIC KEY-----";
    
        private static final BouncyCastleProvider BOUNCY_CASTLE_PROVIDER = new BouncyCastleProvider();
    
        private static byte[] bytesFromKeyStrings(String string) {
            string = string.replaceAll("-----BEGIN ENCRYPTED PRIVATE KEY-----", "");
            string = string.replaceAll("-----BEGIN PRIVATE KEY-----", "");
            string = string.replaceAll("-----BEGIN PUBLIC KEY-----", "");
            string = string.replaceAll("-----BEGIN EC PRIVATE KEY-----", "");
            string = string.replaceAll("-----END EC PRIVATE KEY-----", "");
            string = string.replaceAll("-----END ENCRYPTED PRIVATE KEY-----", "");
            string = string.replaceAll("-----END PRIVATE KEY-----", "");
            string = string.replaceAll("-----END PUBLIC KEY-----", "");
            string = string.replaceAll("\r", "");
            string = string.replaceAll("\n", "");
            return Base64.getDecoder().decode(string);
        }
    
        private static PrivateKey privateKeyFromBytes(byte[] pkcs8key) throws NoSuchAlgorithmException, InvalidKeySpecException, NoSuchProviderException {
            Security.addProvider(BOUNCY_CASTLE_PROVIDER);
            KeyFactory keyFactory = KeyFactory.getInstance("EC", "BC");
            PKCS8EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(pkcs8key);
            return keyFactory.generatePrivate(privateKeySpec);
        }
    
        private static PublicKey publicKeyFromBytes(byte[] x509key) throws NoSuchAlgorithmException, InvalidKeySpecException, NoSuchProviderException {
            Security.addProvider(BOUNCY_CASTLE_PROVIDER);
            KeyFactory keyFactory = KeyFactory.getInstance("EC", "BC");
            X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(x509key);
            return keyFactory.generatePublic(publicKeySpec);
        }
    
        public static void main(String[] args) {
            System.out.println("https://stackoverflow.com/questions/65740255/scalar-is-not-in-the-interval-1-n-1-exception-when-signing-ecdsa-with-sha/65742506?noredirect=1#comment116237430_65742506");
            var content = new byte[100];
            try {
                // signature with private key
                var privateKey = privateKeyFromBytes(bytesFromKeyStrings(privatePem));
                var ecdsaSign = Signature.getInstance("SHA384withECDSA", BOUNCY_CASTLE_PROVIDER);
                ecdsaSign.initSign(privateKey, new SecureRandom());
                ecdsaSign.update(content);
                var signature = ecdsaSign.sign();
    
                // verification with the public key
                var publicKey = publicKeyFromBytes(bytesFromKeyStrings(publicPem));
                var ecdsaVerify = Signature.getInstance("SHA384withECDSA", BOUNCY_CASTLE_PROVIDER);
                ecdsaVerify.initVerify(publicKey);
                ecdsaVerify.update(content);
                boolean signatureVerified = ecdsaVerify.verify(signature);
                System.out.println("signatureVerified: " + signatureVerified);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }