I'm trying to Store ECIES num0 PrivateKey with DEREncodePrivateKey to a std::string and reload it in num1 PrivateKey Object for testing.
Problem is when key is loaded with BERDecodePrivateKey in second PrivateKey object it can't be validated (also tested encryption and decrypting without validation and didn't decrypt )
here's the code
using namespace CryptoPP;
CryptoPP::AutoSeededRandomPool prng;
ECIES<ECP>::PrivateKey pp;
pp.Initialize(prng, ASN1::secp256k1());
/* returns true*/
bool val=pp.Validate(prng, 3);
std::string saves;
StringSink savesink(saves);
pp.DEREncodePrivateKey(savesink);
/*additional unnecessary steps to make sure the key is written completely */
savesink.MessageEnd();
savesink.Flush(true);
ECIES<ECP>::PrivateKey pro;
StringSource savesSource(saves, true);
pro.BERDecodePrivateKey(savesSource,true,savesSource.MaxRetrievable());
/*here the exception is thrown */
pro.ThrowIfInvalid(prng, 3);
finally found what the problem is
as #maarten-bodewes mentioned in comment the DER encoded private exponent doesn't determine the curve OID for the privateKey Object , so before BER Decoding and importing key we need to somehow determine the OID for the Object;
the simplest way is to determine it when Initializing new Object
above code changes to :
ECIES<ECP>::PrivateKey pro;
StringSource savesSource(saves, true);
auto rett = savesSource.MaxRetrievable();
pro.Initialize(prng, ASN1::secp256k1());
pro.BERDecodePrivateKey(savesSource,true,savesSource.MaxRetrievable());
also you AccessGroupParameters().Initialize(/*OID*/); or Initialize(/*OID*/) for existing object
I am generating ECDSA Prime256 keypair using OpenSSL with C++ and trying to import the hex version of the public key using Java. I pass the byte array I obtain from C++ to the following function in java which expects the byte array to be in an X.509 encoded format.
public static PublicKey getPublicKey(byte[] pk) throws NoSuchAlgorithmException, InvalidKeySpecException {
EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(pk);
KeyFactory kf = KeyFactory.getInstance(Constant.KEY_FACTORY_TYPE);
PublicKey pub = kf.generatePublic(publicKeySpec);
return pub;
}
I create an elliptic curve key pair using the following function which retuns an EC_KEY*
EC_KEY* generate_keypair() {
EC_KEY *eckey = EC_KEY_new();
EC_GROUP *ecgroup = EC_GROUP_new_by_curve_name(NID_X9_62_prime256v1);
EC_KEY_set_group(eckey, ecgroup);
EC_KEY_set_asn1_flag(eckey, OPENSSL_EC_NAMED_CURVE);
int kpGenerationStatus = EC_KEY_generate_key(eckey);
if (kpGenerationStatus) {
return eckey;
}
return nullptr;
}
Given the keypair returned by the function above, I want to export the public key to an ASN1.DER format which can be imported using the java method above.
I convert the public key which is of type EC_POINT* to its hex form using EC_POINT_point2hex() by doing the following:
EC_GROUP *ecgroup = EC_GROUP_new_by_curve_name(NID_X9_62_prime256v1);
EC_KEY *keypair = generate_keypair();
char *result = NULL;
BN_CTX *ctx;
ctx = BN_CTX_new();
const EC_POINT *pub = EC_KEY_get0_public_key(keypair);
result = EC_POINT_point2hex(ecgroup, pub, POINT_CONVERSION_UNCOMPRESSED, ctx);
printf("%s\n", result);
Which return the following:
04F588CD1D7103A993D47E53D58C3F40BE8F570604CF2EA01A7657C1423EB19C51BC379F0BEE1FAA60BB9A07DE73EA9BEF7709C1C6429D4051B44F73A458FFB80D
When I inspect this with the ASN.1 decoder I see a message which says Length over 48 bits not supported at position 1 and trying to import it using the java method I receive an error as follows:
java.security.spec.InvalidKeySpecException: java.security.InvalidKeyException: IOException: DerInputStream.getLength(): Should use short form for length
Is there something I am missing while exporting the public key from EC_POINT* to a X.509 Encoded hex string that I can import for validating any signatures?
You are going in the incorrect direction as you want ASN1 base64 value.
EC_POINT_point2hex is converting the internal public key value to hex. It's not in ASN1 format.
You can produce what you want from the command line like so:
Generate EC private key:
openssl ecparam -name prime256v1 -genkey -noout -out key.pem
Extra public key in DER(ASN1) format:
openssl ec -in key.pem -pubout -outform der -out public.cer
Convert to base64
openssl base64 -in .\public.cer
If you take that output and paste it into ASN.1 decoder link it works fine.
Now to turn this into code, you have the EC key generation, but what you want is the steps to:
Generate ASN1 formatted public key
Convert it to base64
To generate the ASN1 formatted public key you want to use the i2d_EC_PUBKEY set of methods and then convert to base64 using BIO_f_base64 filter.
So here is an example problem that when I copy the output to ASN.1 decoder link it works fine.
#include <openssl/bio.h>
#include <openssl/ec.h>
#include <openssl/evp.h>
#include <openssl/x509.h>
EC_KEY* generate_keypair() {
EC_KEY *eckey = EC_KEY_new();
EC_GROUP *ecgroup = EC_GROUP_new_by_curve_name(NID_X9_62_prime256v1);
EC_KEY_set_group(eckey, ecgroup);
EC_KEY_set_asn1_flag(eckey, OPENSSL_EC_NAMED_CURVE);
int kpGenerationStatus = EC_KEY_generate_key(eckey);
if (kpGenerationStatus) {
return eckey;
}
return nullptr;
}
int main()
{
EC_GROUP *ecgroup = EC_GROUP_new_by_curve_name(NID_X9_62_prime256v1);
EC_KEY *keypair = generate_keypair();
BIO* out = BIO_new(BIO_s_mem());
BIO* b64 = BIO_new(BIO_f_base64());
BIO_push(b64, out);
i2d_EC_PUBKEY_bio(b64, keypair);
BIO_flush(b64);
// do what you want this the output in out memory BIO
char* p;
long length = BIO_get_mem_data(out, &p);
// ensure null terminated but copying the buffer into a string to output...
puts(std::string(p, length).c_str());
BIO_free_all(out);
}
I can't complete on the Java side, but if it works with the manual openssl generated base64 string then it will work with the sample application.
I'm playing with cryptopp and have trouble with Base64 encoding/decoding.
In the following code hypothetically sig value should be equal tsig, but they are different on a last character (sig bigger then tsig by one symbol). I've tried also change insertLineBreaks parameter in Base64Encoder but the result is same.
...
RSASSA_PKCS1v15_SHA_Signer privkey(privateKey);
SecByteBlock sbbSignature(privkey.SignatureLength());
privkey.SignMessage(rng, (byte const*) strContents.data(),
strContents.size(),sbbSignature);
Base64Encoder b(new StringSink(signedData));
b.Put(sbbSignature.begin(), sbbSignature.size());
string sig;
StringSink sinksig(sig);
sinksig.Put(sbbSignature.begin(), sbbSignature.size());
string tsig;
StringSource ss(signedData, true,
new Base64Decoder(
new StringSink(tsig)
)
);
Where is my mistake?
b.Put(sbbSignature.begin(), sbbSignature.size());
Try:
b.Put(sbbSignature.begin(), sbbSignature.size());
b.MessgeEnd();
This does not quite look right:
SecByteBlock sbbSignature(privkey.SignatureLength());
privkey.SignMessage(rng, (byte const*) strContents.data(),
strContents.size(),sbbSignature);
Try:
size_t maxLength = privkey.MaxSignatureLength();
SecByteBlock sbbSignature(maxLength);
size_t signatureLength = privkey.SignMessage(rng,
(byte const*) strContents.data(), strContents.size(),
sbbSignature);
if(maxLength != signatureLength)
sbbSignature.resize(signatureLength);
There's an example on the Crypto++ wiki at RSA Signature Scheme with Appendix, but I think its wrong after looking at it.
I have successfully used this some lines ago in my program:
string tmp;
StringSource(msg, true, new PK_EncryptorFilter(*rng, *encryptor, new CryptoPP::HexEncoder(new StringSink(tmp))));
return tmp;
So you know that the Crypto++ objects are well created and so.
Now I want to encrypt a whole binary file and save it to an adjacent file:
FileSource(file.c_str(), true, new PK_EncryptorFilter(*rng, *encryptor, new FileSink((file+".xx").c_str(), true)),true);
But this last line crashes with a debug error stating that abort() has been called.
Hunting down the error, I tried to change the second argument to the FileSource call to false, leading to the following code:
FileSource(file.c_str(), false, new PK_EncryptorFilter(*rng, *encryptor, new FileSink((file+".xx").c_str(), true)),true);
And then the error gone, but the destination file weights 0 bytes, nothing was read/wrote.
I do not know what can can the key to the problem, so, I hope someone can help a little bit.
EDIT: I am using Visual Studio 2013 Pro.
EDIT2: I hunted the error further.
This works and the file binary content is correctly printed on screen:
string s;
FileSource file2("C:\\test.jpg", true, new StringSink(s));
std::cout << s << std::endl;
But this don't work and ends with the mentioned crash.
string s;
FileSource file2("C:\\test.jpg", true, new PK_EncryptorFilter(*rng, *encryptor, new StringSink (s)));
std::cout << s << std::endl;
This is so strange since the same PK_EncryptorFilter filter is used in another method without trouble, as I stated at the beginning of the post.
Anyway, I post here my entire class, so as to get a clear idea of what is going on:
RSASystem::RSASystem()
{
std::string pubkey = "...OMITED...";
rng = new AutoSeededRandomPool;
CryptoPP::HexDecoder decoder;
decoder.Put((byte*)pubkey.c_str(), pubkey.size());
decoder.MessageEnd();
CryptoPP::HexDecoder decoder2;
decoder2.Put((byte*)pubkey.c_str(), pubkey.size());
decoder2.MessageEnd();
verifier = new RSASSA_PKCS1v15_SHA_Verifier;
encryptor = new RSAES_OAEP_SHA_Encryptor;
verifier->AccessKey().Load(decoder);
encryptor->AccessKey().Load(decoder2);
}
string RSASystem::encrypt(string msg)
{
string tmp;
StringSource(msg, true, new PK_EncryptorFilter(*rng, *encryptor, new CryptoPP::HexEncoder(new StringSink(tmp))));
return tmp;
}
void RSASystem::encryptFile(string file)
{
FileSource(file.c_str(), true, new PK_EncryptorFilter(*rng, *encryptor, new FileSink((file+".xx").c_str(), true)),true);
}
EDIT 3: After surrounding the code with try..catch() I got this error:
RSA/OAEP-MGF1(SHA-1): message length of 490986 exceeds the maximum of 214 for this public key
Which now I think can be easily solved.
FileSource(file.c_str(), false,
new PK_EncryptorFilter(*rng, *encryptor,
new FileSink((file+".xx").c_str(), true)
),
true);
This does not look right. new FileSink((file+".xx").c_str() returns a char*, and you need a pointer to a Sink. Plus, there's an extra false in there I'm not used to seeing. Something like:
FileSource fs1(filename, true,
new PK_EncryptorFilter(rng, encryptor,
new FileSink(filename, true)
) // PK_EncryptorFilter
); // StringSource
There's a couple of examples on the Crypto++ wiki. See RSA Cryptography and RSA Encryption Schemes.
The following is an example from the Crypto++ wiki using RSA. But you can use the code for any cryptosystem that adheres to PK_Encryptor and PK_Decryptor (Sources (like StringSource and FileSource) and Sinks (like StringSink and FileSink) are also interchangeable):
////////////////////////////////////////////////
// Generate keys
AutoSeededRandomPool rng;
InvertibleRSAFunction params;
params.GenerateRandomWithKeySize( rng, 1536 );
RSA::PrivateKey privateKey( params );
RSA::PublicKey publicKey( params );
string plain="RSA Encryption", cipher, recovered;
////////////////////////////////////////////////
// Encryption
RSAES_OAEP_SHA_Encryptor e( publicKey );
StringSource ss1( plain, true,
new PK_EncryptorFilter( rng, e,
new StringSink( cipher )
) // PK_EncryptorFilter
); // StringSource
////////////////////////////////////////////////
// Decryption
RSAES_OAEP_SHA_Decryptor d( privateKey );
StringSource ss2( cipher, true,
new PK_DecryptorFilter( rng, d,
new StringSink( recovered )
) // PK_DecryptorFilter
); // StringSource
assert( plain == recovered );
Also, don't use anonymous declarations. Some versions of GCC has problems with them. That is, use:
StringSource ss1( plain, true,
...
rather than:
StringSource( plain, true,
...
I had already pending the encryption and security subject so I wasn't aware of the limitation on the length of the message of the RSA scheme.
https://security.stackexchange.com/questions/44702/whats-the-limit-on-the-size-of-the-data-that-public-key-cryptos-can-handle
So the solution passes by implementing an Integrated or Hybrid Encryption Scheme, like ECIES.
I've done this successfully with Crypto++ using: http://www.cryptopp.com/wiki/Elliptic_Curve_Integrated_Encryption_Scheme
Thanks to jww to point to the correct decision.
OK, I think I know where you might be having problems. But I'd need to see all your code and not just the encryption.
I could coax a BER Decode error by omitting encoder1.MessageEnd and encoder2.MessageEnd. Apparently, I was able to read the key before it was fully written. I assume it was fully written after leaving main (and the destructors ran) because the file sizes looked OK with ls.
In the code below, the message was encrypted under publicKey1 and then decrypted with privateKey2 to ensure the keys were round-tripping.
try {
////////////////////////////////////////////////
// Generate keys
AutoSeededRandomPool rng;
InvertibleRSAFunction params;
params.GenerateRandomWithKeySize(rng, 1024);
RSA::PrivateKey privateKey1(params);
RSA::PublicKey publicKey1(privateKey1);
////////////////////////////////////////////////
// Save/Load keys
HexEncoder encoder1(new FileSink("private-key-der.txt", true));
HexEncoder encoder2(new FileSink("public-key-der.txt", true));
privateKey1.Save(encoder1);
publicKey1.Save(encoder2);
// Must have these. Otherwise, the full key (hex encoded)
// is not written until destructors are run
encoder1.MessageEnd();
encoder2.MessageEnd();
FileSource fs1("private-key-der.txt", true, new HexDecoder);
FileSource fs2("public-key-der.txt", true, new HexDecoder);
RSA::PrivateKey privateKey2;
RSA::PublicKey publicKey2;
privateKey2.Load(fs1);
bool valid = privateKey2.Validate(rng, 3);
if(!valid)
throw Exception(Exception::OTHER_ERROR, "Failed to validate key 1");
publicKey2.Load(fs2);
valid = publicKey2.Validate(rng, 3);
if(!valid)
throw Exception(Exception::OTHER_ERROR, "Failed to validate key 2");
////////////////////////////////////////////////
// Scratch
string plain="RSA Encryption", cipher, recovered;
////////////////////////////////////////////////
// Encryption
RSAES_OAEP_SHA_Encryptor encryptor(publicKey1);
StringSource ss1(plain, true,
new PK_EncryptorFilter(rng, encryptor,
new StringSink(cipher)
) // PK_EncryptorFilter
); // StringSource
////////////////////////////////////////////////
// Decryption
RSAES_OAEP_SHA_Decryptor decryptor(privateKey2);
StringSource ss2(cipher, true,
new PK_DecryptorFilter(rng, decryptor,
new StringSink(recovered)
) // PK_DecryptorFilter
); // StringSource
cout << "Recovered plain text: " << recovered << endl;
} catch (const Exception& ex) {
cerr << ex.what() << endl;
}
I have to get ECDSA signature in variable using Crypto++.
I tried to get it after launching SignMessage but signature is empty.
How could i get it?
Have you had a look at the Crypto++ wiki? There's a lot of stuff on Elliptic Curve Digital Signature Algorithm.
Its not really clear what you are doing or where things went wrong, so here's a copy and paste from the wiki:
Signing:
ECDSA<ECP, SHA1>::PrivateKey privateKey;
privateKey.Load(...);
AutoSeededRandomPool prng;
string message = "Yoda said, Do or do not. There is no try.";
string signature;
StringSource ss1( message, true /*pump all*/,
new SignerFilter( prng,
ECDSA<ECP,SHA1>::Signer( privateKey ),
new StringSink( signature )
) // SignerFilter
); // StringSource
Verification:
ECDSA<ECP, SHA1>::PublicKey publicKey;
publicKey.Load(...);
// Result of the verification process
bool result = false;
// Exactly what was signed in the previous step
string message = ...;
// Output from the signing operation in the previous step
string signature = ...;
StringSource ss2( signature+message, true /*pump all*/,
new SignatureVerificationFilter(
ECDSA<ECP,SHA1>::Verifier(publicKey),
new ArraySink( (byte*)&result, sizeof(result) )
) // SignatureVerificationFilter
);
// Verification failure?
if( !result ) {...}
If you would like the verifcation to throw on a failure, then try:
static const int VERIFICATION_FLAGS = SIGNATURE_AT_BEGIN | THROW_EXCEPTION;
StringSource ss3( signature+message, true /*pump all*/,
new SignatureVerificationFilter(
ECDSA<ECP,SHA1>::Verifier(publicKey),
NULL, /* No need for attached filter */
VERIFICATION_FLAGS
) // SignatureVerificationFilter
);