I am using secp256k1 to sign hash of a text in server and verify the signature on client side .
Now, I need to send actual text as well to the client but my main constraint is bandwidth and I can't add text separately. Therefore, I need to be able to put the actual text inside signature and extract it during verification?
Here is my code
Signing
static final X9ECParameters curve = SECNamedCurves.getByName ("secp256k1");
static final ECDomainParameters domain = new ECDomainParameters(curve.getCurve (), curve.getG (), curve.getN (), curve.getH ());
public byte[] sign (byte[] hash) throws CryptoException
{
if ( priv == null )
{
throw new CryptoException (ErrorCode.KEY_NOT_FOUND, "Need private key to sign");
}
ECDSASigner signer = new ECDSASigner(new HMacDSAKCalculator(new SHA256Digest()));
signer.init (true, new ECPrivateKeyParameters(priv, domain));
BigInteger[] signature = signer.generateSignature (hash);
ByteArrayOutputStream s = new ByteArrayOutputStream();
try
{
DERSequenceGenerator seq = new DERSequenceGenerator(s);
seq.addObject (new ASN1Integer(signature[0]));
seq.addObject (new ASN1Integer(signature[1]));
seq.close ();
return s.toByteArray ();
}
catch ( IOException e )
{
}
return null;
}
Verification
public static boolean verify (byte[] hash, byte[] signature, byte[] pub)
{
ASN1InputStream asn1 = new ASN1InputStream(signature);
try
{
ECDSASigner signer = new ECDSASigner();
signer.init (false, new ECPublicKeyParameters(curve.getCurve ().decodePoint (pub), domain));
DLSequence seq = (DLSequence) asn1.readObject ();
BigInteger r = ((ASN1Integer) seq.getObjectAt (0)).getPositiveValue ();
BigInteger s = ((ASN1Integer) seq.getObjectAt (1)).getPositiveValue ();
return signer.verifySignature (hash, r, s);
}
catch ( Exception e )
{
// threat format errors as invalid signatures
return false;
}
finally
{
try
{
asn1.close ();
}
catch ( IOException e )
{
}
}
}
ECDSA requires a hash over the message to be secure. So all information is lost. Some signature schemes use a hash + part of the message. This is for instance the case for RSA in the ISO/IEC 9796 signature schemes giving (partial) message recovery - which is the technical term of what you (and Dave Thompson) are talking about.
However ECDSA signatures are just big enough to contain a single hash (and usually not even that); there is not much you can do to add data to the hash value. Trying to use EC signatures for partial message recovery is an exercise in futility (also because how verification is performed).
However if you want to use fewer bits you can do still things:
use the most dense representation of message and signature (e.g. just R and S concatenated for ECDSA signatures);
use the smallest key size possible (check https://keylength.com/ for info);
switch to a signature scheme that uses smaller signatures, for instance the BLS signature scheme over elliptic curves - it will halve the signature size compared to ECDSA (using the most efficient encoding).
I've put the BLS scheme last because it is usually not in generic libraries; I consider it the expert way out. You may hit a learning curve.
Signatures themselves don't compress well, by the way, unless you encode them badly. The other disadvantage of compression is that it is usually tricky to calculate a maximum size for all possible messages.
A digital signature, ECDSA or RSA, generally doesn't contain the message (see #dave_thompson_085 comment). An "attached signature" refers to a file that basically contains the message and the digital signature, which can be encoded in a CMS or XMLDsig format.
So if you need to send the message and the signature, just send them together, using a custom or a known format, and compress it additionally to reduce the size
AttachedSignature = zip(format(message + signature))
Related
I need to check if a file is pkcs#8 DER format in Java when uploading the file, I think maybe PKCS8EncodedKeySpec and getFormat() can be used.
class FileFormatePkcs8{
public static void main(String[] args) {
String filename = args[0];
try {
File f = new File(filename);
byte[] encodedKey = new byte[(int)f.length()];
PKCS8EncodedKeySpec pkcs8Key = new PKCS8EncodedKeySpec(encodedKey);
if(pkcs8Key.getFormat().equals("PKCS#8")) {
System.out.println("It's pkcs8.");
}
else {
System.out.println("It's not pkcs8.");
}
}
catch (Exception ex) {
System.out.println("exception:"+ex.getMessage());
}
}
}
All the files input will get the "It's pkcs8." result. I realize "PKCS8EncodedKeySpec" will create the pkcs#8 key, but I don't know using which class to replace it.
Note that: both of PKCS#8 and DER need to be check, so I think org.bouncycastle.openssl.PEMParser can be ignored. Or am I on the wrong track?
First, you don't actually read the contents of the file at all. You just create an empty buffer the same size as the file. Since this buffer doesn't contain any data, the non-present data is not in PKCS8 format, nor JKS format, nor Bitcoin wallet format, nor any other format. (Note instead of multiple steps, you can just use byte[] data = Files.readAllBytes(Paths.get(filename))).
Second, there are two PKCS8 DER formats.
OOTB Java visibly supports PKCS8-unencrypted (clear) PrivateKeyInfo only if you know the algorithm to which the key applies (RSA, DSA, ECDSA/ECDH, etc). If you do, simply call KeyFactory.getInstance for that algorithm, then call .generatePrivateKey with a PKCS8EncodedKeySpec containing the (purported) PKCS8-clear, and if it returns an appropriate subclass of PrivateKey (and doesn't throw an exception) then the data was in fact PKCS8-clear and for that algorithm. If you don't know the algorithm but only a limited set are permitted or possible, you can simply try each in turn and see if any (and which) works.
Otherwise, you must either parse the ASN.1 yourself (possible, but nontrivial), or use BouncyCastle (but not the PEM part): call org.bouncycastle.asn1.pkcs.PrivateKeyInfo.getInstance(der) and if it succeeds the input data either is PKCS8-clear or a very good imitation.
For PKCS8-encrypted, Java does expose javax.crypto.EncryptedPrivateKeyInfo; just invoke the constructor on the data and if it doesn't throw the data looked like PKCS8-encrypted. This does not check however that this data can be decrypted and when decrypted is actually a privatekey as it should be; for that if you know the password use epki.getKeySpec() combined with checking the resulting purported PKCS8-clear as above.
I'm trying to make use of unofficial API documentation. I need to sign all of the requests with my own certificate, but the docs came up only with Java code to use, here is it:
public class EncryptionUtils {
private static final String ALGORITHM_NAME = "SHA1withRSA";
private static final String CERT_TYPE = "pkcs12";
private static final String CONTAINER_NAME = "LoginCert";
private static final String PASSWORD = "CE75EA598C7743AD9B0B7328DED85B06";
public static String signContent(byte[] contents, final InputStream cert) throws IOException, GeneralSecurityException, NullPointerException {
final KeyStore instance = KeyStore.getInstance(CERT_TYPE);
instance.load(cert, PASSWORD.toCharArray());
final PrivateKey privateKey = (PrivateKey) instance.getKey(CONTAINER_NAME, PASSWORD.toCharArray());
final Signature instance2 = Signature.getInstance(ALGORITHM_NAME);
instance2.initSign(privateKey);
instance2.update(contents);
return Base64.getEncoder().encodeToString(instance2.sign());
}}
I came up with this code
private static string password = "CE75EA598C7743AD9B0B7328DED85B06";
public static string Sign(string text, string cert)
{
X509Certificate2 certificate = new X509Certificate2(DecodeCrt(cert), password, X509KeyStorageFlags.Exportable);
RSA provider = (RSA)certificate.PrivateKey;
// Hash the data
var hash = HashText(text);
// Sign the hash
var signature = provider.SignHash(hash, HashAlgorithmName.SHA1, RSASignaturePadding.Pkcs1);
return Convert.ToBase64String(signature);
}
public static byte[] HashText(string text)
{
SHA1Managed sha1Hasher = new SHA1Managed();
UnicodeEncoding encoding = new UnicodeEncoding();
byte[] data = encoding.GetBytes(text);
byte[] hash = sha1Hasher.ComputeHash(data);
return hash;
}
public static byte[] DecodeCrt(string crt)
{
return Convert.FromBase64String(crt);
}
but the output differs from the Java's version.
I've tried to temporarily run java task from c# so I would know if it even works, and it is.
Is there any way to write this in c#?
Both codes use for the signature RSA with Pkcs#1 v1.5 padding and SHA1, which is specified in the Java code via SHA1withRSA and in the C# code explicitly in the RSA#SignHash method via the 2nd and 3rd parameter. Since the Pkcs#1 v1.5 variant for signatures (RSASSA-PKCS1-v1_5) is deterministic, both codes must provide the same signature (assuming the same data to be signed and the same private key).
If this isn't the case, there are actually only two reasonable explanations:
First, the encoding used: In the Java code, the data are transferred as byte[], so that it's not possible to determine which encoding is used based on the posted code. In the C# code, in contrast, the data are passed as a string and converted into a byte[] in HashText. For this the standard-ctor of UnicodeEncoding is used, which applies UTF16LE and a byte order mark (BOM). For UTF8 (without BOM) UTF8Encoding would have to be used. It's crucial that the same encoding is applied in the C# code as in the Java code, otherwise different signatures are usually generated.
Second, the pfx/p12 file: pfx/p12 files can contain several certificates. In the Java code, KeyStore#getKey references the private key with its alias (the same applies to the certificate), in the C# code, X509Certificate2(Byte[], String, X509KeyStorageFlags) references the first certificate in the container, see also here. To ensure that the same certificate/key is referenced in both codes, the pfx/p12 file may therefore contain only exactly one certificate including the corresponding private key.
If this is taken into account, both codes generate the same signature on my machine (assuming the same data to be signed and the same pfx/p12 file).
Finally, it should be noted that in both codes cert is used for different objects. In the Java code, it denotes the InputStream, in the C# code the Base64 encoded binary data of the pfx/pf12 file.
I want to show that if I modify one bit or byte from a given X509 certificate the signature verification results false (because this modification results different hash value from the certificate). I'm stuck in the case that how to do the modification on the certificate using getTBSCertificate() method. My following code does the verification process perfectly BUT I tried to make it fail using bit or byte modification's idea but it doesn't work. Note that this idea that I proposed is to proof that any modification on the certificate will make a failure while signature verification
public class VerifyX509 {
private static Certificate getCACert;
private static Certificate[] getCert;
public static void main(String[] args) throws CertificateEncodingException {
setURLConnection("https://www.google.com");
X509Certificate x509cert= (X509Certificate) getCert[0];
byte[] b= x509cert.getTBSCertificate();
b[0] = (byte) ~b[0];
// HOW TO UPDATE getTBSCertificate() after flipping the b[0] to make Verify() in my method verifySign() return false!
verifySign();
}
public static void setURLConnection(String link){
try{
int i=1;
URL destinationURL = new URL(link);
HttpsURLConnection con = (HttpsURLConnection) destinationURL.openConnection();
con.connect();
getCert = con.getServerCertificates();
for (Certificate c : getCert)
{
if (i==2)
{
getCACert= c;
return;
}
i+=1;
}
}catch (Exception e1) {
JOptionPane.showMessageDialog(null, "Error while connection! Check your Internet Connection.");
e1.printStackTrace();
}
}
public static boolean verifySign()
{
try
{
getCert[0].verify(getCACert.getPublicKey());
return true;
} catch (GeneralSecurityException e2)
{
return false;
}
}
}
How can I setup proof-of-concept code to show that the verification while fail?
Note that this idea that I proposed is to proof that any modification on the certificate will make a failure while signature verification.
You can demonstrate this (to a certain probability of correctness) by simply flipping random bits in valid certificates and then attempting to validate them.
However, you cannot prove anything like this. A proper proof requires:
A mathematical proof that a properly implement X509 certificate has the property that changing the certificate renders it invalid (with some probability very close to one1).
A formal-methods proof that the code that is loading the certificate and doing the verification is correctly implemented.
1 - In fact, it is easy to see that the probability cannot be exactly one. Apply the pigeonhole principle.
byte[] b= x509cert.getTBSCertificate();
b[0] = (byte) ~b[0];
Changing a byte in an array that you have obtained from the certificate doesn't change the certificate.
You would have to reload it from the byte array using a CertificateFactory.
Mr. Mike, all what you have to do is to get the row data DER-encoded certificate information (TBS part) and you can extract it as below
URL url = new URL("https://www.google.com/");
HttpsURLConnection con = (HttpsURLConnection) url.openConnection();
con.connect();
Certificate userCert[] = con.getServerCertificates();
X509Certificate x509cert = ((X509Certificate) userCert[0]);
byte[] tbs=x509cert.getTBSCertificate();
Then copy the content of the array b to another array bcopy through a loop and do what ever modifications you want (i.e by using the masking technique Anding with x55) after that you can get the hash value through
String sha1 = "";
MessageDigest crypt = MessageDigest.getInstance("SHA-1");
crypt.reset();
crypt.update(bcopy);
sha1 = byteToHex(crypt.digest());
private static String byteToHex(final byte[] hash)
{
Formatter formatter = new Formatter();
for (byte b : hash)
{
formatter.format("%02x", b);
}
String result = formatter.toString();
formatter.close();
return result;
}
at this point you have the hash value of the modified certificate, you can go now and extract the signature from the original certificate [ byte[] sig= x509cert.getSignature(); ] and decrypt the signature to get the hash value and compare it with the modified hash value, good luck ;)
If you look at RFC 5280, the cert has 3 fields:
tbsCertificate
signatureAlgorithm
signatureValue
The signatureValue is the very last item in the certificate.
I had a similar requirement. These are the steps I followed:
Have 1 certificate in .crt format (base-64 encoded) file. The cert is enclosed between "-----BEGIN CERTIFICATE-----" and "-----END CERTIFICATE-----"
Edit the very last character of the certificate on the line just before the END CERTIFICATE line. Just add 1 to that character or reduce by 1. If the last character is x, make it either y or w.
This will change the signature in the certificate and the signature won't be valid anymore.
I am writing a test harness in java for a program relating to the ikev2 protocol. As part of this i need to be able to calculate an ECDSA signature (specifically using the NIST P-256 curve).
RFC 4754 Describes the the use of ECDSA in IKEv2 and helpfully provides a set of test vectors (Including for the p256 curve that i need).
I am trying to run the ECDSA-256 Test Vector values (Section 8.1 in the RFC) through java's ECDSA signature implementation using the following code:
//"abc" for the input
byte[] input = { 0x61, 0x62, 0x63 };
//Ugly way of getting the ECParameterSpec for the P-256 curve by name as opposed to specifying all the parameters manually.
KeyPairGenerator kpg = KeyPairGenerator.getInstance("EC");
ECGenParameterSpec kpgparams = new ECGenParameterSpec("secp256r1");
kpg.initialize(kpgparams);
ECParameterSpec params = ((ECPublicKey) kpg.generateKeyPair().getPublic()).getParams();
//Create the static private key W from the Test Vector
ECPrivateKeySpec static_privates = new ECPrivateKeySpec(new BigInteger("DC51D3866A15BACDE33D96F992FCA99DA7E6EF0934E7097559C27F1614C88A7F", 16), params);
KeyFactory kf = KeyFactory.getInstance("EC");
ECPrivateKey spriv = (ECPrivateKey) kf.generatePrivate(static_privates);
//Perfrom ECDSA signature of the data with SHA-256 as the hash algorithm
Signature dsa = Signature.getInstance("SHA256withECDSA");
dsa.initSign(spriv);
dsa.update(input);
byte[] output = dsa.sign();
System.out.println("Result: " + new BigInteger(1, output).toString(16));
The result should be:
CB28E099 9B9C7715 FD0A80D8 E47A7707 9716CBBF 917DD72E
97566EA1 C066957C 86FA3BB4 E26CAD5B F90B7F81 899256CE 7594BB1E A0C89212
748BFF3B 3D5B0315
Instead I get:
30460221 00dd9131 edeb5efd c5e718df c8a7ab2d 5532b85b 7d4c012a e5a4e90c 3b824ab5 d7022100 9a8a2b12 9e10a2ff 7066ff79 89aa73d5 ba37c868 5ec36517 216e2e43 ffa876d7
I know that the length difference is due to Java ASN.1 Encoding the signature. However, the rest of it is completely wrong and I'm stumped as to why.
Any help or advice would be greatly appreciated!
P.S I am not a ECDSA or Java crypto expert so it is probably a stupid mistake I am making
I'm guessing that each time you run your program, you get a different signature value for the same plaintext (to-be-signed) input.
ECDSA specifies that a random ephemeral ECDSA private key be generated per signature. To that end, Signature.getInstance("SHA256withECDSA") doesn't let you specify an ephemeral key (this is a good thing, to prevent many a self shot in the foot!). Instead, it gets its own SecureRandom instance that will make your output nondeterministic.
This probably means you can't use JCE (Signature.getInstance()) for test vector validation.
What you could do is extend SecureRandom in a way that it returns deterministic data. Obviously you shouldn't use this in a real deployment:
public class FixedSecureRandom extends SecureRandom {
private static boolean debug = false;
private static final long serialVersionUID = 1L;
public FixedSecureRandom() { }
private int nextBytesIndex = 0;
private byte[] nextBytesValues = null;
public void setBytes(byte[] values) {
this.nextBytesValues = values;
}
public void nextBytes(byte[] b) {
if (nextBytesValues==null) {
super.nextBytes(b);
} else if (nextBytesValues.length==0) {
super.nextBytes(b);
} else {
for (int i=0; i<b.length; i++) {
b[i] = nextBytesValues[nextBytesIndex];
nextBytesIndex = (nextBytesIndex + 1) % nextBytesValues.length;
}
}
}
}
Phew. Ok now you have a SecureRandom class that returns you some number of known bytes, then falls back to a real SecureRandom after that. I'll say it again (excuse the shouting) - DO NOT USE THIS IN PRODUCTION!
Next you'll need to use a ECDSA implementation that lets you specify your own SecureRandom. You can use BouncyCastle's ECDSASigner for this purpose. Except here you're going to give it your own bootlegged FixedSecureRandom, so that when it calls secureRandom.getBytes(), it gets the bytes you want it to. This lets you control the ephemeral key to match that specified in the test vectors. You may need to massage the actual bytes (eg. add zero pre-padding) to match what ECDSASigner is going to request.
ECPrivateKeyParameters ecPriv = ...; // this is the user's EC private key (not ephemeral)
FixedSecureRandom fsr_k = new FixedSecureRandom();
fsr_k.setBytes(tempKeyK);
ECDSASigner signer = new ECDSASigner();
ParametersWithRandom ecdsaprivrand = new ParametersWithRandom(ecPriv, fsr_k);
signer.init(true, ecdsaprivrand);
Note that BC's ECDSASigner implements only the EC signature part, not the hashing. You'll still need to do your own hashing (assuming your input data is in data):
Digest md = new SHA256Digest()
md.reset();
md.update(data, 0, data.length);
byte[] hash = new byte[md.getDigestSize()];
md.doFinal(hash, 0);
before you create the ECDSA signature:
BigInteger[] sig = signer.generateSignature(hash);
Finally, this BigInteger[] (should be length==2) are the (r,s) values. You'll need to ASN.1 DER-encode it, which should give you the droids bytes you're looking for.
here's my complete test following tsechin's solution using BouncyCastle but sticking to good old JCA API:
byte[] input = { 0x61, 0x62, 0x63 };
//Create the static private key W from the Test Vector
ECNamedCurveParameterSpec parameterSpec = ECNamedCurveTable.getParameterSpec("secp256r1");
org.bouncycastle.jce.spec.ECPrivateKeySpec privateKeySpec = new org.bouncycastle.jce.spec.ECPrivateKeySpec(new BigInteger("DC51D3866A15BACDE33D96F992FCA99DA7E6EF0934E7097559C27F1614C88A7F", 16), parameterSpec);
KeyFactory kf = KeyFactory.getInstance("EC");
ECPrivateKey spriv = (ECPrivateKey) kf.generatePrivate(privateKeySpec);
//Perfrom ECDSA signature of the data with SHA-256 as the hash algorithm
Signature dsa = Signature.getInstance("SHA256withECDSA", "BC");
FixedSecureRandom random = new FixedSecureRandom();
random.setBytes(Hex.decode("9E56F509196784D963D1C0A401510EE7ADA3DCC5DEE04B154BF61AF1D5A6DECE"));
dsa.initSign(spriv, random);
dsa.update(input);
byte[] output = dsa.sign();
// compare the signature with the expected reference values
ASN1Sequence sequence = ASN1Sequence.getInstance(output);
DERInteger r = (DERInteger) sequence.getObjectAt(0);
DERInteger s = (DERInteger) sequence.getObjectAt(1);
Assert.assertEquals(r.getValue(), new BigInteger("CB28E0999B9C7715FD0A80D8E47A77079716CBBF917DD72E97566EA1C066957C", 16));
Assert.assertEquals(s.getValue(), new BigInteger("86FA3BB4E26CAD5BF90B7F81899256CE7594BB1EA0C89212748BFF3B3D5B0315", 16));
I write the following code for signing the message and then verify it, in java by Bouncy Castle.
signing work properly but verifying not work. the result of code print:
signature tampered
can not recover
and return null.
why eng.hasFullMessage() function return false and why the following code doesn't work?thanks all.
public static String sigVer(PublicKey pu, PrivateKey pr, String original) throws Exception{
//sign
BigInteger big = ((RSAKey) pu).getModulus();
byte[] text = original.getBytes();
RSAKeyParameters rsaPriv = new RSAKeyParameters(true, big,((RSAPrivateKey) pr).getPrivateExponent());
RSAKeyParameters rsaPublic = new RSAKeyParameters(false, big,((RSAPublicKey) pu).getPublicExponent());
RSAEngine rsa = new RSAEngine();
byte[] data;
Digest dig = new SHA1Digest();
ISO9796d2Signer eng = new ISO9796d2Signer(rsa, dig, true);
eng.init(true, rsaPriv);
eng.update(text[0]);
eng.update(text, 1, text.length - 1);
data = eng.generateSignature();
String signature = data.toString();
//verify
eng = new ISO9796d2Signer(rsa, dig, true);
eng.init(false, rsaPublic);
text = signature.getBytes();
if (!eng.verifySignature(text)) {
System.out.println("signature tampered");
}
try{
if (eng.hasFullMessage()) {
eng.updateWithRecoveredMessage(signature.getBytes());
}
byte[] message = eng.getRecoveredMessage();
String ss = message.toString();
return ss;
}
catch (Exception e) {
System.out.println("can not recover");
return null;
}
}
Actually to verify large messages you have to provide the input for verification which is the original message and not only the signature which works only for full recovery
//verify
eng = new ISO9796d2Signer(rsa, dig, true);
eng.init(false, rsaPublic);
// when verifying there has to be also the original plain text
eng.update(text,0,text.length);
signBytes = signature.getBytes();
if (!eng.verifySignature(signBytes)) {
System.out.println("signature tampered");
}
I played around with this method and also receive the errors. In addition the "update" method mentioned does not work and the "updateWithRecoveredMessage" does not exist.
After some testing I figured that:
I have to sue getBytes("UTF-8") and String s = new String(message, "UTF-8")
I can use up to 234 bytes in the signature, with 235 it will break. (guess it would be 256 bytes but some are used for padding/markers)
If I understand correctly the problem is that the signer only allows a certain amount of bytes to be included in the signature.
If you use a longer payload you MUST include the payload in the verification step as well:
eng.init(false, rsaPublic);
byte[] message = payload.getBytes("UTF-8");
eng.update(message, 0, message.length);
then verification works just fine and you get the first part of the payload with eng.getRecoveredMessage().
For me this defeated the purpose, so I went another way.
The workaround I use is to two a two-step approach:
1) I generate a short key which is stored in the signature
2) use a symmetric (i.e. AES) to encrypt the large payload. I also generate a hash for the payload.
3) the key and hash for the payload is the one included in the signature.
Most of the times we don't have the original message to update and verifiy by the signature. On the other hand the length is limited to 234 bytes.