Skip to content

Instantly share code, notes, and snippets.

@irbull
Created August 11, 2016 18:32
Show Gist options
  • Save irbull/08339ddcd5686f509e9826964b17bb59 to your computer and use it in GitHub Desktop.
Save irbull/08339ddcd5686f509e9826964b17bb59 to your computer and use it in GitHub Desktop.
Code signing and verification with OpenSSL
#include <iostream>
#include <openssl/aes.h>
#include <openssl/evp.h>
#include <openssl/rsa.h>
#include <openssl/pem.h>
#include <openssl/ssl.h>
#include <openssl/bio.h>
#include <openssl/err.h>
#include <assert.h>
std::string privateKey ="-----BEGIN RSA PRIVATE KEY-----\n"\
"MIIEowIBAAKCAQEAy8Dbv8prpJ/0kKhlGeJYozo2t60EG8L0561g13R29LvMR5hy\n"\
"vGZlGJpmn65+A4xHXInJYiPuKzrKUnApeLZ+vw1HocOAZtWK0z3r26uA8kQYOKX9\n"\
"Qt/DbCdvsF9wF8gRK0ptx9M6R13NvBxvVQApfc9jB9nTzphOgM4JiEYvlV8FLhg9\n"\
"yZovMYd6Wwf3aoXK891VQxTr/kQYoq1Yp+68i6T4nNq7NWC+UNVjQHxNQMQMzU6l\n"\
"WCX8zyg3yH88OAQkUXIXKfQ+NkvYQ1cxaMoVPpY72+eVthKzpMeyHkBn7ciumk5q\n"\
"gLTEJAfWZpe4f4eFZj/Rc8Y8Jj2IS5kVPjUywQIDAQABAoIBADhg1u1Mv1hAAlX8\n"\
"omz1Gn2f4AAW2aos2cM5UDCNw1SYmj+9SRIkaxjRsE/C4o9sw1oxrg1/z6kajV0e\n"\
"N/t008FdlVKHXAIYWF93JMoVvIpMmT8jft6AN/y3NMpivgt2inmmEJZYNioFJKZG\n"\
"X+/vKYvsVISZm2fw8NfnKvAQK55yu+GRWBZGOeS9K+LbYvOwcrjKhHz66m4bedKd\n"\
"gVAix6NE5iwmjNXktSQlJMCjbtdNXg/xo1/G4kG2p/MO1HLcKfe1N5FgBiXj3Qjl\n"\
"vgvjJZkh1as2KTgaPOBqZaP03738VnYg23ISyvfT/teArVGtxrmFP7939EvJFKpF\n"\
"1wTxuDkCgYEA7t0DR37zt+dEJy+5vm7zSmN97VenwQJFWMiulkHGa0yU3lLasxxu\n"\
"m0oUtndIjenIvSx6t3Y+agK2F3EPbb0AZ5wZ1p1IXs4vktgeQwSSBdqcM8LZFDvZ\n"\
"uPboQnJoRdIkd62XnP5ekIEIBAfOp8v2wFpSfE7nNH2u4CpAXNSF9HsCgYEA2l8D\n"\
"JrDE5m9Kkn+J4l+AdGfeBL1igPF3DnuPoV67BpgiaAgI4h25UJzXiDKKoa706S0D\n"\
"4XB74zOLX11MaGPMIdhlG+SgeQfNoC5lE4ZWXNyESJH1SVgRGT9nBC2vtL6bxCVV\n"\
"WBkTeC5D6c/QXcai6yw6OYyNNdp0uznKURe1xvMCgYBVYYcEjWqMuAvyferFGV+5\n"\
"nWqr5gM+yJMFM2bEqupD/HHSLoeiMm2O8KIKvwSeRYzNohKTdZ7FwgZYxr8fGMoG\n"\
"PxQ1VK9DxCvZL4tRpVaU5Rmknud9hg9DQG6xIbgIDR+f79sb8QjYWmcFGc1SyWOA\n"\
"SkjlykZ2yt4xnqi3BfiD9QKBgGqLgRYXmXp1QoVIBRaWUi55nzHg1XbkWZqPXvz1\n"\
"I3uMLv1jLjJlHk3euKqTPmC05HoApKwSHeA0/gOBmg404xyAYJTDcCidTg6hlF96\n"\
"ZBja3xApZuxqM62F6dV4FQqzFX0WWhWp5n301N33r0qR6FumMKJzmVJ1TA8tmzEF\n"\
"yINRAoGBAJqioYs8rK6eXzA8ywYLjqTLu/yQSLBn/4ta36K8DyCoLNlNxSuox+A5\n"\
"w6z2vEfRVQDq4Hm4vBzjdi3QfYLNkTiTqLcvgWZ+eX44ogXtdTDO7c+GeMKWz4XX\n"\
"uJSUVL5+CVjKLjZEJ6Qc2WZLl94xSwL71E41H4YciVnSCQxVc4Jw\n"\
"-----END RSA PRIVATE KEY-----\n\0";
std::string publicKey ="-----BEGIN PUBLIC KEY-----\n"\
"MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAy8Dbv8prpJ/0kKhlGeJY\n"\
"ozo2t60EG8L0561g13R29LvMR5hyvGZlGJpmn65+A4xHXInJYiPuKzrKUnApeLZ+\n"\
"vw1HocOAZtWK0z3r26uA8kQYOKX9Qt/DbCdvsF9wF8gRK0ptx9M6R13NvBxvVQAp\n"\
"fc9jB9nTzphOgM4JiEYvlV8FLhg9yZovMYd6Wwf3aoXK891VQxTr/kQYoq1Yp+68\n"\
"i6T4nNq7NWC+UNVjQHxNQMQMzU6lWCX8zyg3yH88OAQkUXIXKfQ+NkvYQ1cxaMoV\n"\
"PpY72+eVthKzpMeyHkBn7ciumk5qgLTEJAfWZpe4f4eFZj/Rc8Y8Jj2IS5kVPjUy\n"\
"wQIDAQAB\n"\
"-----END PUBLIC KEY-----\n";
RSA* createPrivateRSA(std::string key) {
RSA *rsa = NULL;
const char* c_string = key.c_str();
BIO * keybio = BIO_new_mem_buf((void*)c_string, -1);
if (keybio==NULL) {
return 0;
}
rsa = PEM_read_bio_RSAPrivateKey(keybio, &rsa,NULL, NULL);
return rsa;
}
RSA* createPublicRSA(std::string key) {
RSA *rsa = NULL;
BIO *keybio;
const char* c_string = key.c_str();
keybio = BIO_new_mem_buf((void*)c_string, -1);
if (keybio==NULL) {
return 0;
}
rsa = PEM_read_bio_RSA_PUBKEY(keybio, &rsa,NULL, NULL);
return rsa;
}
bool RSASign( RSA* rsa,
const unsigned char* Msg,
size_t MsgLen,
unsigned char** EncMsg,
size_t* MsgLenEnc) {
EVP_MD_CTX* m_RSASignCtx = EVP_MD_CTX_create();
EVP_PKEY* priKey = EVP_PKEY_new();
EVP_PKEY_assign_RSA(priKey, rsa);
if (EVP_DigestSignInit(m_RSASignCtx,NULL, EVP_sha256(), NULL,priKey)<=0) {
return false;
}
if (EVP_DigestSignUpdate(m_RSASignCtx, Msg, MsgLen) <= 0) {
return false;
}
if (EVP_DigestSignFinal(m_RSASignCtx, NULL, MsgLenEnc) <=0) {
return false;
}
*EncMsg = (unsigned char*)malloc(*MsgLenEnc);
if (EVP_DigestSignFinal(m_RSASignCtx, *EncMsg, MsgLenEnc) <= 0) {
return false;
}
EVP_MD_CTX_cleanup(m_RSASignCtx);
return true;
}
bool RSAVerifySignature( RSA* rsa,
unsigned char* MsgHash,
size_t MsgHashLen,
const char* Msg,
size_t MsgLen,
bool* Authentic) {
*Authentic = false;
EVP_PKEY* pubKey = EVP_PKEY_new();
EVP_PKEY_assign_RSA(pubKey, rsa);
EVP_MD_CTX* m_RSAVerifyCtx = EVP_MD_CTX_create();
if (EVP_DigestVerifyInit(m_RSAVerifyCtx,NULL, EVP_sha256(),NULL,pubKey)<=0) {
return false;
}
if (EVP_DigestVerifyUpdate(m_RSAVerifyCtx, Msg, MsgLen) <= 0) {
return false;
}
int AuthStatus = EVP_DigestVerifyFinal(m_RSAVerifyCtx, MsgHash, MsgHashLen);
if (AuthStatus==1) {
*Authentic = true;
EVP_MD_CTX_cleanup(m_RSAVerifyCtx);
return true;
} else if(AuthStatus==0){
*Authentic = false;
EVP_MD_CTX_cleanup(m_RSAVerifyCtx);
return true;
} else{
*Authentic = false;
EVP_MD_CTX_cleanup(m_RSAVerifyCtx);
return false;
}
}
void Base64Encode( const unsigned char* buffer,
size_t length,
char** base64Text) {
BIO *bio, *b64;
BUF_MEM *bufferPtr;
b64 = BIO_new(BIO_f_base64());
bio = BIO_new(BIO_s_mem());
bio = BIO_push(b64, bio);
BIO_write(bio, buffer, length);
BIO_flush(bio);
BIO_get_mem_ptr(bio, &bufferPtr);
BIO_set_close(bio, BIO_NOCLOSE);
BIO_free_all(bio);
*base64Text=(*bufferPtr).data;
}
size_t calcDecodeLength(const char* b64input) {
size_t len = strlen(b64input), padding = 0;
if (b64input[len-1] == '=' && b64input[len-2] == '=') //last two chars are =
padding = 2;
else if (b64input[len-1] == '=') //last char is =
padding = 1;
return (len*3)/4 - padding;
}
void Base64Decode(const char* b64message, unsigned char** buffer, size_t* length) {
BIO *bio, *b64;
int decodeLen = calcDecodeLength(b64message);
*buffer = (unsigned char*)malloc(decodeLen + 1);
(*buffer)[decodeLen] = '\0';
bio = BIO_new_mem_buf(b64message, -1);
b64 = BIO_new(BIO_f_base64());
bio = BIO_push(b64, bio);
*length = BIO_read(bio, *buffer, strlen(b64message));
BIO_free_all(bio);
}
char* signMessage(std::string privateKey, std::string plainText) {
RSA* privateRSA = createPrivateRSA(privateKey);
unsigned char* encMessage;
char* base64Text;
size_t encMessageLength;
RSASign(privateRSA, (unsigned char*) plainText.c_str(), plainText.length(), &encMessage, &encMessageLength);
Base64Encode(encMessage, encMessageLength, &base64Text);
free(encMessage);
return base64Text;
}
bool verifySignature(std::string publicKey, std::string plainText, char* signatureBase64) {
RSA* publicRSA = createPublicRSA(publicKey);
unsigned char* encMessage;
size_t encMessageLength;
bool authentic;
Base64Decode(signatureBase64, &encMessage, &encMessageLength);
bool result = RSAVerifySignature(publicRSA, encMessage, encMessageLength, plainText.c_str(), plainText.length(), &authentic);
return result & authentic;
}
int main() {
std::string plainText = "My secret message.\n";
char* signature = signMessage(privateKey, plainText);
bool authentic = verifySignature(publicKey, "My secret message.\n", signature);
if ( authentic ) {
std::cout << "Authentic" << std::endl;
} else {
std::cout << "Not Authentic" << std::endl;
}
}
@radj
Copy link

radj commented Jan 24, 2017

Thank you so much for this sample code! Made my life easier. Had a hard time resolving private key sign and public key verify codes in other sources. And this just works.

@joysher
Copy link

joysher commented May 27, 2017

I am getting the below error while compiling:
In function ‘void Base64Decode(const char*, unsigned char**, size_t*)’:
testverify2.cpp:166:39: error: invalid conversion from ‘const void*’ to ‘void*’ [-fpermissive]
bio = BIO_new_mem_buf(b64message, -1);
^
In file included from /usr/include/openssl/evp.h:75:0,
from testverify2.cpp:3:
/usr/include/openssl/bio.h:668:6: error: initializing argument 1 of ‘BIO* BIO_new_mem_buf(void*, int)’ [-fpermissive]
BIO *BIO_new_mem_buf(void *buf, int len);
Please help me to resolve.

@mgibson91
Copy link

This is great! Thanks for taking the time to put this together

@Mathadon
Copy link

Mathadon commented Jan 18, 2018

I am getting the same error as @joysher . I fixed it by changing

void Base64Decode(const char* b64message, unsigned char** buffer, size_t* length) {

to

void Base64Decode(char* b64message, unsigned char** buffer, size_t* length) {

@Mathadon
Copy link

@irbull Thank you for sharing this example. After two days of trying to get other code to work, this was up and running in half an hour.. What is the license for using this code please?

@pbraga
Copy link

pbraga commented Jul 11, 2018

@irbull +1, thanks for this very useful code, would like to know how you license this code.

@chiffacff
Copy link

hello. if i sign text line in php and then verify it if c++ (using this example) I get Not Authentic. Can any one help, what is problem?

@LeMoussel
Copy link

EVP_MD_CTX_cleanup() has removed. I replaced it with EVP_MD_CTX_free()
Ref : https://www.openssl.org/news/cl110.txt

@tawmoto
Copy link

tawmoto commented Aug 29, 2019

Great thanks for the example, probably the best example on the internet.

@nicraMarcin
Copy link

nicraMarcin commented Sep 16, 2019

I've problem with compile. Debian 9, gcc version 8.3.0, libssl-dev installed

$ g++ -lssl -lcrypto  -std=c++11  verifyrsa.cpp 
verifyrsa.cpp: In function ‘bool RSASign(RSA*, const unsigned char*, size_t, unsigned char**, size_t*)’:
verifyrsa.cpp:94:3: error: ‘EVP_MD_CTX_cleanup’ was not declared in this scope
   EVP_MD_CTX_cleanup(m_RSASignCtx);
   ^~~~~~~~~~~~~~~~~~
verifyrsa.cpp:94:3: note: suggested alternative: ‘EVP_MD_CTX_create’
   EVP_MD_CTX_cleanup(m_RSASignCtx);
   ^~~~~~~~~~~~~~~~~~
   EVP_MD_CTX_create
verifyrsa.cpp: In function ‘bool RSAVerifySignature(RSA*, unsigned char*, size_t, const char*, size_t, bool*)’:
verifyrsa.cpp:118:5: error: ‘EVP_MD_CTX_cleanup’ was not declared in this scope
     EVP_MD_CTX_cleanup(m_RSAVerifyCtx);
     ^~~~~~~~~~~~~~~~~~
verifyrsa.cpp:118:5: note: suggested alternative: ‘EVP_MD_CTX_create’
     EVP_MD_CTX_cleanup(m_RSAVerifyCtx);
     ^~~~~~~~~~~~~~~~~~
     EVP_MD_CTX_create
verifyrsa.cpp:122:5: error: ‘EVP_MD_CTX_cleanup’ was not declared in this scope
     EVP_MD_CTX_cleanup(m_RSAVerifyCtx);
     ^~~~~~~~~~~~~~~~~~
verifyrsa.cpp:122:5: note: suggested alternative: ‘EVP_MD_CTX_create’
     EVP_MD_CTX_cleanup(m_RSAVerifyCtx);
     ^~~~~~~~~~~~~~~~~~
     EVP_MD_CTX_create
verifyrsa.cpp:126:5: error: ‘EVP_MD_CTX_cleanup’ was not declared in this scope
     EVP_MD_CTX_cleanup(m_RSAVerifyCtx);
     ^~~~~~~~~~~~~~~~~~
verifyrsa.cpp:126:5: note: suggested alternative: ‘EVP_MD_CTX_create’
     EVP_MD_CTX_cleanup(m_RSAVerifyCtx);
     ^~~~~~~~~~~~~~~~~~
     EVP_MD_CTX_create

This is beacause of changes in openSSL v1.1
https://wiki.openssl.org/index.php/OpenSSL_1.1.0_Changes

@abcdef123ghi
Copy link

abcdef123ghi commented Nov 22, 2019

I tried this code within VS 2019 on Windows,there are some issues:
#1 the funtionalities which are Base64Encode , Base64Decode doesn't work ,you have to replace them with something else
#2 you have to replace all the EVP_MD_CTX_cleanup with EVP_MD_CTX_free as well
beside the issue above anything else works fine so far

@programonauta
Copy link

programonauta commented Jan 20, 2020

Hi,

Your code is exactly what I as looking for. Thanks for that.

But I'm facing some problems to find some symbols.

I'm using Ubuntu subsytem on Windows 10. As it came with an old version of openssl I updated it:

wget https://www.openssl.org/source/openssl-1.1.1a.tar.gz
tar -zxf openssl-1.1.1a.tar.gz && cd openssl-1.1.1a
./config
make
make test # Verify possible erros
sudo mv /usr/bin/openssl ~/tmp # backup current version
sudo make install
sudo ln -s /usr/local/bin/openssl /usr/bin/openssl # create symbolic link
sudo ldconfig # update de symlinks and rebuild library caches

Follow @abcdef123ghi comment, I replaced all EVP_MD_CTX_cleanup with EVP_MD_CTX_free.

I noticed that strlen and padding functions produced errors as well, that I solved including the #include <string.h>

But, there was another error, it's not possible find EVP_MD_CTX_new and EVP_MD_CTX_free:

$ g++ signVerify.cpp -o signVerify -I/usr/include/openssl/ -L/usr/bin/openssl -lssl -lcry
pto
/tmp/ccqw7GSo.o: In function `RSASign(rsa_st*, unsigned char const*, unsigned long, unsigned char**, unsigned long*)':
signVerify.cpp:(.text+0x139): undefined reference to `EVP_MD_CTX_new'
signVerify.cpp:(.text+0x231): undefined reference to `EVP_MD_CTX_free'
/tmp/ccqw7GSo.o: In function `RSAVerifySignature(rsa_st*, unsigned char*, unsigned long, char const*, unsigned long, bool*)':
signVerify.cpp:(.text+0x282): undefined reference to `EVP_MD_CTX_new'
signVerify.cpp:(.text+0x31b): undefined reference to `EVP_MD_CTX_free'
signVerify.cpp:(.text+0x33b): undefined reference to `EVP_MD_CTX_free'
signVerify.cpp:(.text+0x355): undefined reference to `EVP_MD_CTX_free'
collect2: error: ld returned 1 exit status

Any suggestion? How can I find the exactly library where this functions must be?

Thanks

@programonauta
Copy link

programonauta commented Jan 20, 2020

Hey guys,

I'v just found the error, and just to register here:

It was necessary remove libssl-dev

sudo apt-get remove libssl-dev

@gauravrathi85
Copy link

Maybe a dumb question , but I do not see a free after malloc. Did I miss something?

@gauravrathi85
Copy link

I want to say that this code is helpful and does work. But be careful as there are memory leaks here (I had to check all openssl functions used here and find the function used to free the allocated memory and then verify that there were no memory leaks). Apart from openssl functions the malloc is never freed, so keep that in mind

@supriya1658
Copy link

I am getting access violation exception at EVP_DigestVerifyFinal api. Not able to understand why as pointer its data and memory pointing is correct

@supriya1658
Copy link

can please anybody suggest anything

@Tarasus
Copy link

Tarasus commented Apr 2, 2020

I don't know what am I doing wrong, but
bool authentic = verifySignature(publicKey, "My secret message.\n", signature);
always returns false
I have replaced all EVP_MD_CTX_cleanup() with EVP_MD_CTX_free() but this might not be the case.
my EVP_DigestVerifyFinal() returns 0
ERR_get_error() and ERR_error_string() called after it returns "err 67108983 = error:04000077:rsa routines::wrong signature length"

Update: I got rid of all base64 encoding-decoding in my code, and signature-text verification worked like a charm!
BIO_read(bio, *buffer, strlen(b64message)); was returning 0, so EVP_DigestVerifyFinal() returned errors.

@0xGhost
Copy link

0xGhost commented May 13, 2020

add
(*base64Text)[(*bufferPtr).length] = '\0';
after line 146
will fix bugs about base64 encoding

@0xGhost
Copy link

0xGhost commented May 13, 2020

I don't know what am I doing wrong, but
bool authentic = verifySignature(publicKey, "My secret message.\n", signature);
always returns false
I have replaced all EVP_MD_CTX_cleanup() with EVP_MD_CTX_free() but this might not be the case.
my EVP_DigestVerifyFinal() returns 0
ERR_get_error() and ERR_error_string() called after it returns "err 67108983 = error:04000077:rsa routines::wrong signature length"

Update: I got rid of all base64 encoding-decoding in my code, and signature-text verification worked like a charm!
BIO_read(bio, *buffer, strlen(b64message)); was returning 0, so EVP_DigestVerifyFinal() returned errors.

this encoder forget to add '\0' at the end of the char* string
see my comment for fixing

@scottlvsha
Copy link

Why don't you just use return value of function RSAVerifySignature(...) to indicate verification is successful or failure?

@nazzak
Copy link

nazzak commented Dec 7, 2021

Hi, Thank you for sharing this, great work.
When I run Valgrind it is reporting some memory leaks. I think it comes from *b64 which is created in the heap but never freed. I don't think it is freed internally by libcrypto (on Linux).
I'll investigate more and let you know.

@zhjb7
Copy link

zhjb7 commented Apr 20, 2022

Hi, Thank you for sharing this, great work. When I run Valgrind it is reporting some memory leaks. I think it comes from *b64 which is created in the heap but never freed. I don't think it is freed internally by libcrypto (on Linux). I'll investigate more and let you know.

I fixed this memory leaks. This is my fixed codes.
`#include
#include <openssl/aes.h>
#include <openssl/evp.h>
#include <openssl/rsa.h>
#include <openssl/pem.h>
#include <openssl/ssl.h>
#include <openssl/bio.h>
#include <openssl/err.h>
#include <assert.h>
#include <string.h>

std::string privateKey ="-----BEGIN RSA PRIVATE KEY-----\n"
"MIIEowIBAAKCAQEAy8Dbv8prpJ/0kKhlGeJYozo2t60EG8L0561g13R29LvMR5hy\n"
"vGZlGJpmn65+A4xHXInJYiPuKzrKUnApeLZ+vw1HocOAZtWK0z3r26uA8kQYOKX9\n"
"Qt/DbCdvsF9wF8gRK0ptx9M6R13NvBxvVQApfc9jB9nTzphOgM4JiEYvlV8FLhg9\n"
"yZovMYd6Wwf3aoXK891VQxTr/kQYoq1Yp+68i6T4nNq7NWC+UNVjQHxNQMQMzU6l\n"
"WCX8zyg3yH88OAQkUXIXKfQ+NkvYQ1cxaMoVPpY72+eVthKzpMeyHkBn7ciumk5q\n"
"gLTEJAfWZpe4f4eFZj/Rc8Y8Jj2IS5kVPjUywQIDAQABAoIBADhg1u1Mv1hAAlX8\n"
"omz1Gn2f4AAW2aos2cM5UDCNw1SYmj+9SRIkaxjRsE/C4o9sw1oxrg1/z6kajV0e\n"
"N/t008FdlVKHXAIYWF93JMoVvIpMmT8jft6AN/y3NMpivgt2inmmEJZYNioFJKZG\n"
"X+/vKYvsVISZm2fw8NfnKvAQK55yu+GRWBZGOeS9K+LbYvOwcrjKhHz66m4bedKd\n"
"gVAix6NE5iwmjNXktSQlJMCjbtdNXg/xo1/G4kG2p/MO1HLcKfe1N5FgBiXj3Qjl\n"
"vgvjJZkh1as2KTgaPOBqZaP03738VnYg23ISyvfT/teArVGtxrmFP7939EvJFKpF\n"
"1wTxuDkCgYEA7t0DR37zt+dEJy+5vm7zSmN97VenwQJFWMiulkHGa0yU3lLasxxu\n"
"m0oUtndIjenIvSx6t3Y+agK2F3EPbb0AZ5wZ1p1IXs4vktgeQwSSBdqcM8LZFDvZ\n"
"uPboQnJoRdIkd62XnP5ekIEIBAfOp8v2wFpSfE7nNH2u4CpAXNSF9HsCgYEA2l8D\n"
"JrDE5m9Kkn+J4l+AdGfeBL1igPF3DnuPoV67BpgiaAgI4h25UJzXiDKKoa706S0D\n"
"4XB74zOLX11MaGPMIdhlG+SgeQfNoC5lE4ZWXNyESJH1SVgRGT9nBC2vtL6bxCVV\n"
"WBkTeC5D6c/QXcai6yw6OYyNNdp0uznKURe1xvMCgYBVYYcEjWqMuAvyferFGV+5\n"
"nWqr5gM+yJMFM2bEqupD/HHSLoeiMm2O8KIKvwSeRYzNohKTdZ7FwgZYxr8fGMoG\n"
"PxQ1VK9DxCvZL4tRpVaU5Rmknud9hg9DQG6xIbgIDR+f79sb8QjYWmcFGc1SyWOA\n"
"SkjlykZ2yt4xnqi3BfiD9QKBgGqLgRYXmXp1QoVIBRaWUi55nzHg1XbkWZqPXvz1\n"
"I3uMLv1jLjJlHk3euKqTPmC05HoApKwSHeA0/gOBmg404xyAYJTDcCidTg6hlF96\n"
"ZBja3xApZuxqM62F6dV4FQqzFX0WWhWp5n301N33r0qR6FumMKJzmVJ1TA8tmzEF\n"
"yINRAoGBAJqioYs8rK6eXzA8ywYLjqTLu/yQSLBn/4ta36K8DyCoLNlNxSuox+A5\n"
"w6z2vEfRVQDq4Hm4vBzjdi3QfYLNkTiTqLcvgWZ+eX44ogXtdTDO7c+GeMKWz4XX\n"
"uJSUVL5+CVjKLjZEJ6Qc2WZLl94xSwL71E41H4YciVnSCQxVc4Jw\n"
"-----END RSA PRIVATE KEY-----\n\0";

std::string publicKey ="-----BEGIN PUBLIC KEY-----\n"
"MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAy8Dbv8prpJ/0kKhlGeJY\n"
"ozo2t60EG8L0561g13R29LvMR5hyvGZlGJpmn65+A4xHXInJYiPuKzrKUnApeLZ+\n"
"vw1HocOAZtWK0z3r26uA8kQYOKX9Qt/DbCdvsF9wF8gRK0ptx9M6R13NvBxvVQAp\n"
"fc9jB9nTzphOgM4JiEYvlV8FLhg9yZovMYd6Wwf3aoXK891VQxTr/kQYoq1Yp+68\n"
"i6T4nNq7NWC+UNVjQHxNQMQMzU6lWCX8zyg3yH88OAQkUXIXKfQ+NkvYQ1cxaMoV\n"
"PpY72+eVthKzpMeyHkBn7ciumk5qgLTEJAfWZpe4f4eFZj/Rc8Y8Jj2IS5kVPjUy\n"
"wQIDAQAB\n"
"-----END PUBLIC KEY-----\n";

RSA* createPrivateRSA(std::string key) {
RSA rsa = NULL;
const char
c_string = key.c_str();
BIO * keybio = BIO_new_mem_buf((void*)c_string, -1);
if (keybio==NULL) {
return 0;
}
rsa = PEM_read_bio_RSAPrivateKey(keybio, &rsa,NULL, NULL);
BIO_free(keybio);
return rsa;
}

RSA* createPublicRSA(std::string key) {
RSA rsa = NULL;
BIO keybio;
const char
c_string = key.c_str();
keybio = BIO_new_mem_buf((void
)c_string, -1);
if (keybio==NULL) {
return 0;
}
rsa = PEM_read_bio_RSA_PUBKEY(keybio, &rsa,NULL, NULL);
BIO_free(keybio);
return rsa;
}

bool RSASign( RSA* rsa,
const unsigned char* Msg,
size_t MsgLen,
unsigned char** EncMsg,
size_t* MsgLenEnc) {
EVP_MD_CTX* m_RSASignCtx = EVP_MD_CTX_create();
EVP_PKEY* priKey = EVP_PKEY_new();
EVP_PKEY_assign_RSA(priKey, rsa);
if (EVP_DigestSignInit(m_RSASignCtx,NULL, EVP_sha256(), NULL,priKey)<=0) {
EVP_PKEY_free(priKey);
return false;
}
if (EVP_DigestSignUpdate(m_RSASignCtx, Msg, MsgLen) <= 0) {
EVP_PKEY_free(priKey);
return false;
}
if (EVP_DigestSignFinal(m_RSASignCtx, NULL, MsgLenEnc) <=0) {
EVP_PKEY_free(priKey);
return false;
}
EncMsg = (unsigned char)malloc(*MsgLenEnc);
if (EVP_DigestSignFinal(m_RSASignCtx, *EncMsg, MsgLenEnc) <= 0) {
EVP_PKEY_free(priKey);
return false;
}
EVP_PKEY_free(priKey);
EVP_MD_CTX_free(m_RSASignCtx);
return true;
}

bool RSAVerifySignature( RSA* rsa,
unsigned char* MsgHash,
size_t MsgHashLen,
const char* Msg,
size_t MsgLen,
bool* Authentic) {
Authentic = false;
EVP_PKEY
pubKey = EVP_PKEY_new();
EVP_PKEY_assign_RSA(pubKey, rsa);
EVP_MD_CTX* m_RSAVerifyCtx = EVP_MD_CTX_create();

if (EVP_DigestVerifyInit(m_RSAVerifyCtx,NULL, EVP_sha256(),NULL,pubKey)<=0) {
EVP_PKEY_free(pubKey);
return false;
}
if (EVP_DigestVerifyUpdate(m_RSAVerifyCtx, Msg, MsgLen) <= 0) {
EVP_PKEY_free(pubKey);
return false;
}
int AuthStatus = EVP_DigestVerifyFinal(m_RSAVerifyCtx, MsgHash, MsgHashLen);
if (AuthStatus==1) {
*Authentic = true;
EVP_PKEY_free(pubKey);
EVP_MD_CTX_free(m_RSAVerifyCtx);
return true;
} else if(AuthStatus==0){
*Authentic = false;
EVP_PKEY_free(pubKey);
EVP_MD_CTX_free(m_RSAVerifyCtx);
return true;
} else{
*Authentic = false;
EVP_PKEY_free(pubKey);
EVP_MD_CTX_free(m_RSAVerifyCtx);
return false;
}
}

void Base64Encode( const unsigned char* buffer,
size_t length,
char** base64Text) {
BIO *bio, *b64;
BUF_MEM *bufferPtr;

b64 = BIO_new(BIO_f_base64());
bio = BIO_new(BIO_s_mem());
bio = BIO_push(b64, bio);

BIO_write(bio, buffer, length);
BIO_flush(bio);
BIO_get_mem_ptr(bio, &bufferPtr);
BIO_set_close(bio, BIO_NOCLOSE);
BIO_free_all(bio);

*base64Text=(*bufferPtr).data;
(*base64Text)[(*bufferPtr).length] = '\0';
}

size_t calcDecodeLength(const char* b64input) {
size_t len = strlen(b64input), padding = 0;

if (b64input[len-1] == '=' && b64input[len-2] == '=') //last two chars are =
padding = 2;
else if (b64input[len-1] == '=') //last char is =
padding = 1;
return (len*3)/4 - padding;
}

void Base64Decode(const char* b64message, unsigned char** buffer, size_t* length) {
BIO *bio, *b64;

int decodeLen = calcDecodeLength(b64message);
buffer = (unsigned char)malloc(decodeLen + 1);
(*buffer)[decodeLen] = '\0';

bio = BIO_new_mem_buf(b64message, -1);
b64 = BIO_new(BIO_f_base64());
bio = BIO_push(b64, bio);

*length = BIO_read(bio, *buffer, strlen(b64message));
BIO_free_all(bio);
}

char* signMessage(std::string privateKey, std::string plainText) {
RSA* privateRSA = createPrivateRSA(privateKey);
unsigned char* encMessage;
char* base64Text;
size_t encMessageLength;
RSASign(privateRSA, (unsigned char*) plainText.c_str(), plainText.length(), &encMessage, &encMessageLength);
Base64Encode(encMessage, encMessageLength, &base64Text);
free(encMessage);
return base64Text;
}

bool verifySignature(std::string publicKey, std::string plainText, char* signatureBase64) {
RSA* publicRSA = createPublicRSA(publicKey);
unsigned char* encMessage;
size_t encMessageLength;
bool authentic;
Base64Decode(signatureBase64, &encMessage, &encMessageLength);
bool result = RSAVerifySignature(publicRSA, encMessage, encMessageLength, plainText.c_str(), plainText.length(), &authentic);
free(encMessage);
return result & authentic;
}

int main() {
std::string plainText = "My secret message.\n";
char* signature = signMessage(privateKey, plainText);
bool authentic = verifySignature(publicKey, "My secret message.\n", signature);
if ( authentic ) {
std::cout << "Authentic" << std::endl;
} else {
std::cout << "Not Authentic" << std::endl;
}
}

`

@rpolasek-sde
Copy link

rpolasek-sde commented Feb 15, 2023

there are still memory leaks. one of them can be solved by adding free(signature); as a last line in the main() and then there is 1 memory leak remaining which i haven't found yet:

==28980== HEAP SUMMARY:
==28980==     in use at exit: 32 bytes in 1 blocks
==28980==   total heap usage: 11,284 allocs, 11,283 frees, 1,362,954 bytes allocated
==28980==
==28980== 32 bytes in 1 blocks are definitely lost in loss record 1 of 1
==28980==    at 0x4848899: malloc (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==28980==    by 0x4A2B41D: CRYPTO_zalloc (in /usr/lib/x86_64-linux-gnu/libcrypto.so.3)
==28980==    by 0x496AD13: BUF_MEM_new (in /usr/lib/x86_64-linux-gnu/libcrypto.so.3)
==28980==    by 0x496AD6C: BUF_MEM_new_ex (in /usr/lib/x86_64-linux-gnu/libcrypto.so.3)
==28980==    by 0x4958E99: ??? (in /usr/lib/x86_64-linux-gnu/libcrypto.so.3)
==28980==    by 0x494FA13: BIO_new_ex (in /usr/lib/x86_64-linux-gnu/libcrypto.so.3)
==28980==    by 0x10AC23: Base64Encode(unsigned char const*, unsigned long, char**) (openssl_example_new.cc:165)
==28980==    by 0x10AEF5: signMessage(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >) (openssl_example_new.cc:211)
==28980==    by 0x10B0DA: main (openssl_example_new.cc:232)
==28980==
==28980== LEAK SUMMARY:
==28980==    definitely lost: 32 bytes in 1 blocks
==28980==    indirectly lost: 0 bytes in 0 blocks
==28980==      possibly lost: 0 bytes in 0 blocks
==28980==    still reachable: 0 bytes in 0 blocks
==28980==         suppressed: 0 bytes in 0 blocks
==28980==
==28980== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)

btw. how could we replace 3 deprecated functions?

/home/xyz/abc/cc/openssl/openssl_example_new.cc:59:37: warning: ‘RSA* PEM_read_bio_RSAPrivateKey(BIO*, RSA**, int (*)(char*, int, int, void*), void*)’ is deprecated: Since OpenSSL 3.0 [-Wdeprecated-declarations]
   59 |     rsa = PEM_read_bio_RSAPrivateKey(keybio, &rsa, nullptr, nullptr);
      |           ~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
In file included from /home/xyz/abc/cc/openssl/openssl_example_new.cc:7:
/usr/include/openssl/pem.h:447:1: note: declared here
  447 | DECLARE_PEM_rw_cb_attr(OSSL_DEPRECATEDIN_3_0, RSAPrivateKey, RSA)
      | ^~~~~~~~~~~~~~~~~~~~~~
/home/xyz/abc/cc/openssl/openssl_example_new.cc: In function ‘RSA* createPublicRSA(std::string)’:
/home/xzy/abc/cc/openssl/openssl_example_new.cc:87:34: warning: ‘RSA* PEM_read_bio_RSA_PUBKEY(BIO*, RSA**, int (*)(char*, int, int, void*), void*)’ is deprecated: Since OpenSSL 3.0 [-Wdeprecated-declarations]
   87 |     rsa = PEM_read_bio_RSA_PUBKEY(keybio, &rsa,nullptr, nullptr);
      |           ~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/usr/include/openssl/pem.h:449:1: note: declared here
  449 | DECLARE_PEM_rw_attr(OSSL_DEPRECATEDIN_3_0, RSA_PUBKEY, RSA)
      | ^~~~~~~~~~~~~~~~~~~
[100%] Linking CXX executable openssl_example_new
[100%] Built target openssl_example_new
Authentic

@rpolasek-sde
Copy link

i've just found last memory leaks. you can fix it by adding free(bufferPtr); as a last line in the Base64Encode() but that's not much nice code at all...

@frankz61
Copy link

It really helped me. i think the part of base64 code can`t peer to current standard. use some other popular base64 open source library can make it more compatible.

@PiaoL
Copy link

PiaoL commented Apr 22, 2024

It really helped me, i'm using Objective-C

@ahuigo
Copy link

ahuigo commented May 23, 2024

for openssl>=1.1.0 use EVP_MD_CTX_free instead of EVP_MD_CTX_cleanup. It works for me on my linux.

#include <iostream>
#include <openssl/aes.h>
#include <openssl/evp.h>
#include <openssl/rsa.h>
#include <openssl/pem.h>
#include <openssl/ssl.h>
#include <openssl/bio.h>
#include <openssl/err.h>
#include <assert.h>
#include <cstring>

/**
g++  b.cpp -o main -lcryptopp -lssl -lcrypto  2>&1  && ./main
*/

std::string privateKey ="-----BEGIN RSA PRIVATE KEY-----\n"\
"MIIEowIBAAKCAQEAy8Dbv8prpJ/0kKhlGeJYozo2t60EG8L0561g13R29LvMR5hy\n"\
"vGZlGJpmn65+A4xHXInJYiPuKzrKUnApeLZ+vw1HocOAZtWK0z3r26uA8kQYOKX9\n"\
"Qt/DbCdvsF9wF8gRK0ptx9M6R13NvBxvVQApfc9jB9nTzphOgM4JiEYvlV8FLhg9\n"\
"yZovMYd6Wwf3aoXK891VQxTr/kQYoq1Yp+68i6T4nNq7NWC+UNVjQHxNQMQMzU6l\n"\
"WCX8zyg3yH88OAQkUXIXKfQ+NkvYQ1cxaMoVPpY72+eVthKzpMeyHkBn7ciumk5q\n"\
"gLTEJAfWZpe4f4eFZj/Rc8Y8Jj2IS5kVPjUywQIDAQABAoIBADhg1u1Mv1hAAlX8\n"\
"omz1Gn2f4AAW2aos2cM5UDCNw1SYmj+9SRIkaxjRsE/C4o9sw1oxrg1/z6kajV0e\n"\
"N/t008FdlVKHXAIYWF93JMoVvIpMmT8jft6AN/y3NMpivgt2inmmEJZYNioFJKZG\n"\
"X+/vKYvsVISZm2fw8NfnKvAQK55yu+GRWBZGOeS9K+LbYvOwcrjKhHz66m4bedKd\n"\
"gVAix6NE5iwmjNXktSQlJMCjbtdNXg/xo1/G4kG2p/MO1HLcKfe1N5FgBiXj3Qjl\n"\
"vgvjJZkh1as2KTgaPOBqZaP03738VnYg23ISyvfT/teArVGtxrmFP7939EvJFKpF\n"\
"1wTxuDkCgYEA7t0DR37zt+dEJy+5vm7zSmN97VenwQJFWMiulkHGa0yU3lLasxxu\n"\
"m0oUtndIjenIvSx6t3Y+agK2F3EPbb0AZ5wZ1p1IXs4vktgeQwSSBdqcM8LZFDvZ\n"\
"uPboQnJoRdIkd62XnP5ekIEIBAfOp8v2wFpSfE7nNH2u4CpAXNSF9HsCgYEA2l8D\n"\
"JrDE5m9Kkn+J4l+AdGfeBL1igPF3DnuPoV67BpgiaAgI4h25UJzXiDKKoa706S0D\n"\
"4XB74zOLX11MaGPMIdhlG+SgeQfNoC5lE4ZWXNyESJH1SVgRGT9nBC2vtL6bxCVV\n"\
"WBkTeC5D6c/QXcai6yw6OYyNNdp0uznKURe1xvMCgYBVYYcEjWqMuAvyferFGV+5\n"\
"nWqr5gM+yJMFM2bEqupD/HHSLoeiMm2O8KIKvwSeRYzNohKTdZ7FwgZYxr8fGMoG\n"\
"PxQ1VK9DxCvZL4tRpVaU5Rmknud9hg9DQG6xIbgIDR+f79sb8QjYWmcFGc1SyWOA\n"\
"SkjlykZ2yt4xnqi3BfiD9QKBgGqLgRYXmXp1QoVIBRaWUi55nzHg1XbkWZqPXvz1\n"\
"I3uMLv1jLjJlHk3euKqTPmC05HoApKwSHeA0/gOBmg404xyAYJTDcCidTg6hlF96\n"\
"ZBja3xApZuxqM62F6dV4FQqzFX0WWhWp5n301N33r0qR6FumMKJzmVJ1TA8tmzEF\n"\
"yINRAoGBAJqioYs8rK6eXzA8ywYLjqTLu/yQSLBn/4ta36K8DyCoLNlNxSuox+A5\n"\
"w6z2vEfRVQDq4Hm4vBzjdi3QfYLNkTiTqLcvgWZ+eX44ogXtdTDO7c+GeMKWz4XX\n"\
"uJSUVL5+CVjKLjZEJ6Qc2WZLl94xSwL71E41H4YciVnSCQxVc4Jw\n"\
"-----END RSA PRIVATE KEY-----\n\0";

std::string publicKey ="-----BEGIN PUBLIC KEY-----\n"\
"MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAy8Dbv8prpJ/0kKhlGeJY\n"\
"ozo2t60EG8L0561g13R29LvMR5hyvGZlGJpmn65+A4xHXInJYiPuKzrKUnApeLZ+\n"\
"vw1HocOAZtWK0z3r26uA8kQYOKX9Qt/DbCdvsF9wF8gRK0ptx9M6R13NvBxvVQAp\n"\
"fc9jB9nTzphOgM4JiEYvlV8FLhg9yZovMYd6Wwf3aoXK891VQxTr/kQYoq1Yp+68\n"\
"i6T4nNq7NWC+UNVjQHxNQMQMzU6lWCX8zyg3yH88OAQkUXIXKfQ+NkvYQ1cxaMoV\n"\
"PpY72+eVthKzpMeyHkBn7ciumk5qgLTEJAfWZpe4f4eFZj/Rc8Y8Jj2IS5kVPjUy\n"\
"wQIDAQAB\n"\
"-----END PUBLIC KEY-----\n";

RSA* createPrivateRSA(std::string key) {
  RSA *rsa = NULL;
  const char* c_string = key.c_str();
  BIO * keybio = BIO_new_mem_buf((void*)c_string, -1);
  if (keybio==NULL) {
      return 0;
  }
  rsa = PEM_read_bio_RSAPrivateKey(keybio, &rsa,NULL, NULL);
  return rsa;
}

RSA* createPublicRSA(std::string key) {
  RSA *rsa = NULL;
  BIO *keybio;
  const char* c_string = key.c_str();
  keybio = BIO_new_mem_buf((void*)c_string, -1);
  if (keybio==NULL) {
      return 0;
  }
  rsa = PEM_read_bio_RSA_PUBKEY(keybio, &rsa,NULL, NULL);
  return rsa;
}

bool RSASign( RSA* rsa,
              const unsigned char* Msg,
              size_t MsgLen,
              unsigned char** EncMsg,
              size_t* MsgLenEnc) {
  EVP_MD_CTX* m_RSASignCtx = EVP_MD_CTX_create();
  EVP_PKEY* priKey  = EVP_PKEY_new();
  EVP_PKEY_assign_RSA(priKey, rsa);
  if (EVP_DigestSignInit(m_RSASignCtx,NULL, EVP_sha256(), NULL,priKey)<=0) {
      return false;
  }
  if (EVP_DigestSignUpdate(m_RSASignCtx, Msg, MsgLen) <= 0) {
      return false;
  }
  if (EVP_DigestSignFinal(m_RSASignCtx, NULL, MsgLenEnc) <=0) {
      return false;
  }
  *EncMsg = (unsigned char*)malloc(*MsgLenEnc);
  if (EVP_DigestSignFinal(m_RSASignCtx, *EncMsg, MsgLenEnc) <= 0) {
      return false;
  }
//   EVP_MD_CTX_free(m_RSASignCtx);
  EVP_MD_CTX_free(m_RSASignCtx);
  return true;
}

bool RSAVerifySignature( RSA* rsa,
                         unsigned char* MsgHash,
                         size_t MsgHashLen,
                         const char* Msg,
                         size_t MsgLen,
                         bool* Authentic) {
  *Authentic = false;
  EVP_PKEY* pubKey  = EVP_PKEY_new();
  EVP_PKEY_assign_RSA(pubKey, rsa);
  EVP_MD_CTX* m_RSAVerifyCtx = EVP_MD_CTX_create();

  if (EVP_DigestVerifyInit(m_RSAVerifyCtx,NULL, EVP_sha256(),NULL,pubKey)<=0) {
    return false;
  }
  if (EVP_DigestVerifyUpdate(m_RSAVerifyCtx, Msg, MsgLen) <= 0) {
    return false;
  }
  int AuthStatus = EVP_DigestVerifyFinal(m_RSAVerifyCtx, MsgHash, MsgHashLen);
  if (AuthStatus==1) {
    *Authentic = true;
    EVP_MD_CTX_free(m_RSAVerifyCtx);
    return true;
  } else if(AuthStatus==0){
    *Authentic = false;
    EVP_MD_CTX_free(m_RSAVerifyCtx);
    return true;
  } else{
    *Authentic = false;
    EVP_MD_CTX_free(m_RSAVerifyCtx);
    return false;
  }
}

void Base64Encode( const unsigned char* buffer,
                   size_t length,
                   char** base64Text) {
  BIO *bio, *b64;
  BUF_MEM *bufferPtr;

  b64 = BIO_new(BIO_f_base64());
  bio = BIO_new(BIO_s_mem());
  bio = BIO_push(b64, bio);

  BIO_write(bio, buffer, length);
  BIO_flush(bio);
  BIO_get_mem_ptr(bio, &bufferPtr);
  BIO_set_close(bio, BIO_NOCLOSE);
  BIO_free_all(bio);

  *base64Text=(*bufferPtr).data;
}

size_t calcDecodeLength(const char* b64input) {
  size_t len = strlen(b64input), padding = 0;

  if (b64input[len-1] == '=' && b64input[len-2] == '=') //last two chars are =
    padding = 2;
  else if (b64input[len-1] == '=') //last char is =
    padding = 1;
  return (len*3)/4 - padding;
}

void Base64Decode(const char* b64message, unsigned char** buffer, size_t* length) {
  BIO *bio, *b64;

  int decodeLen = calcDecodeLength(b64message);
  *buffer = (unsigned char*)malloc(decodeLen + 1);
  (*buffer)[decodeLen] = '\0';

  bio = BIO_new_mem_buf(b64message, -1);
  b64 = BIO_new(BIO_f_base64());
  bio = BIO_push(b64, bio);

  *length = BIO_read(bio, *buffer, strlen(b64message));
  BIO_free_all(bio);
}

char* signMessage(std::string privateKey, std::string plainText) {
  RSA* privateRSA = createPrivateRSA(privateKey); 
  unsigned char* encMessage;
  char* base64Text;
  size_t encMessageLength;
  RSASign(privateRSA, (unsigned char*) plainText.c_str(), plainText.length(), &encMessage, &encMessageLength);
  Base64Encode(encMessage, encMessageLength, &base64Text);
  free(encMessage);
  return base64Text;
}

bool verifySignature(std::string publicKey, std::string plainText, char* signatureBase64) {
  RSA* publicRSA = createPublicRSA(publicKey);
  unsigned char* encMessage;
  size_t encMessageLength;
  bool authentic;
  Base64Decode(signatureBase64, &encMessage, &encMessageLength);
  bool result = RSAVerifySignature(publicRSA, encMessage, encMessageLength, plainText.c_str(), plainText.length(), &authentic);
  return result & authentic;
}

int main() {
  std::string plainText = "My secret message.\n";
  char* signature = signMessage(privateKey, plainText);
  bool authentic = verifySignature(publicKey, "My secret message.\n", signature);
  if ( authentic ) {
    std::cout << "Authentic" << std::endl;
  } else {
    std::cout << "Not Authentic" << std::endl;
  }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment