I've been working on writing a signer service for an Ethereum transaction manager and I need to sign Ethereum transactions using Google KMS Golang APIs. I'll try and summarise the problems I'm facing below.
Ethereum requires compact RLP encoded 65-byte ECDSA signatures in R || S || V format. ECDSA signatures by Google KMS on the other hand have extra header components (R length, S length, etc) along with variable length R and S components. This makes these signatures incompatible for use with Ethereum transaction signing.
A way to get around this is parsing the R and S bytes from the ecdsa signature obtained from Google KMS, compute and add the V byte to the end and use this signature to get a signed Ethereum transaction. Something like this:
var parsedSig struct{ R, S *big.Int }
_, err = asn1.Unmarshal(body, &parsedSig)
if err != nil {
logger.WithError(err).Error("failed to parse signature bytes")
return err
}
However this would possibly fail due to one or more of the following reasons:
Creating compact ECDSA signatures of 65-byte length by parsing out the R and S components and adding the V component is possibly as distrustful as it sounds. R and S components as mentioned above are not always of 32 byte length for standard ECDSA signatures, which means that the ECDSA signature created by concatenating the components might not always result in 64 bytes.
Currently signed transactions in Ethereum are created from Keccak-256 digest hashes after RLP encoding transactions as shown below:
// from go-ethereum
func rlpHash(x interface{}) (h common.Hash) {
hw := sha3.NewLegacyKeccak256()
rlp.Encode(hw, x)
hw.Sum(h[:0])
return h
}
Asymmetric ECDSA key signing in Google KMS doesn’t have support for Keccak-256 SHA3 message digests. Would using a SHA-256 digest for ethereum transactions work? IMO this would fail since all transaction signature verification happens on RLP encoded Keccak hashes.
At this point I am not very sure how to compute the V component of the ECDSA signature after having checked the secp256k1 implementation of the secp256k1_ecdsa_sign_recoverable() function.
How do I go about solving these above issues to be able to create verifiable signed Ethereum transactions using asymmetric elliptic curve signing algorithm by Google KMS?
You can use GCP to sign Ethereum transactions (with secp256k1). It requires some calculation for the 'v' value though. Here's a library with the full procedure:
https://pkg.go.dev/github.com/pascaldekloe/etherkeyms#v0.1.0/google
Would using a SHA-256 digest for ethereum transactions work?
I was having the same doubts there. The curve calculation does not care about the hash algorithm for as far as I know. Maybe Google uses the classification for the size only? Either way, SHA-256 works just fine here.
https://github.com/pascaldekloe/etherkeyms/blob/096d712031548e601994c859637009eb53a08e34/google/google.go#L101
Related
I am using Azure key vault for creating and storing my Secp256k1 keys. I am also using the sign API for getting my input string signed. I am working on a Secp256K1 blockchain network.These are steps I follow to get the signature in Golang.
Converting my Hex string into Byte[]
Sha256 of this Byte[]
RawURL encoding of this Sha.
b64.RawURLEncoding.EncodeToString(sha)
Sending this to Key vault for signature.
Decoding the response using RawURLEncoding.
b64.RawURLEncoding.DecodeString(*keyOpsResp.Result)
Doing Hex of the []Byte array returned from 5th Step.
Sending the signature to the blockchain.
The problem I am facing is that signature is invalid sometimes. As in 2/5 times it works and other times signature verification fails.
I am thinking there is some special chars or padding thing that I am missing.
How can I resolve this?
PS: Azure uses non-deterministic signatures where as chains usually use deterministic signs. I did some reading and found out that for verification it does not matter both could be verified successfully. Let me know if I am wrong.
• Since you are using base64 encode RawURL for encoding purposes, you can check whether the following parts are included in the token request for the keyvault signature validation. They are as follows: -
aud (audience): The resource of the token. Notice that this is https://vault.azure.net. This token will NOT work for any resource that does not explicitly match this value, such as graph.
iat (issued at): The number of ticks since the start of the epoch when the token was issued.
nbf (not before): The number of ticks since the start of the epoch when this token becomes valid.
exp (expiration): The number of ticks since the start of the epoch when this token expires.
appid (application ID): The GUID for the application ID making this request.
tid (tenant ID): The GUID for the tenant ID of the principal making this request. It is important that all the values be properly identified in the token for the request to work
• Also, please check the size of the block that is dependent on the target key and the algorithm to be used for validation of signature. In that, please check the ‘decryptParameters’, ‘algorithm’ and ‘ciphertext’ parameter for the returns that are displayed after the decrypt operation during signature validation.
Please find the below links for more details: -
https://learn.microsoft.com/en-us/java/api/com.azure.security.keyvault.keys.cryptography.cryptographyasyncclient.decrypt?view=azure-java-stable
I will start with a disclaimer that I am out of my depth here. A colleague was showing me a decryption routine he wrote with pycryptodomex. He had an encrypted file, a key, and a nonce (extracted from the file). He was able to decrypt the file contents in a very straight forward way.
c = Crypto.Cipher.AES.new(key, AES.MODE_GCM, nonce)
c.decrypt(encrypted_data)
You can see a similar implementation in the pycryptodome test for GCM:
cipher = AES.new(self.key_128, AES.MODE_GCM, nonce=self.nonce_96)
pt = get_tag_random("plaintext", 16 * 100)
ct = cipher.encrypt(pt)
cipher = AES.new(self.key_128, AES.MODE_GCM, nonce=self.nonce_96)
pt2 = cipher.decrypt(ct)
Unfortunately, pycryptdomex is an additional dependency that I would need to carry around and I am looking to avoid this. I have a base installation of Anaconda, which brings with it the pyCrypto and pyCA/cryptography packages. It appears that pycryptodomex is a fork of pyCrytpo, which didn't have a stable GCM implementation to begin with. When I look at the implementation for PyCA/cryptography, it looks straight forward:
cipher = Cipher(algorithms.AES(key), modes.GCM(nonce), backend=default_backend())
d = cipher.decryptor()
But when we want to decrypt content we have to call finalize_with_tag and produce an authentication tag:
d.update(encrypted_data) + d.finalize_with_tag(tag)
Unfortunately, I don't have an authentication tag nor do I know where to find it. I can't set the value to None as there is a minimum length requirement. I'm also not sure why I need to produce an authentication tag in the first place for AES GCM decryption with PyCA/Cryptography but I do not need to produce a tag when decrypting with the pycryptodomex. I'm ultimately looking for clarity on the following:
Is it possible to implement AES/GCM decryption with the Anaconda PyCA/cryptography package if I only have access to the key, nonce, and encrypted data?
Why do I need to provide an authentication tag for decryption with one implementation and not the other?
Is pycryptodomex doing something under the hood to determine the tag?
GCM without authentication tag is equivalent to CTR mode. (except the + 1 difference in starting counter value)
Calling decrypt does not verify the tag (as far as I know). You can test this yourself by altering the ciphertext just one byte. It will decrypt just fine (to a plaintext that is off by one byte). Use decrypt_and_verify (see test_invalid_mac test).
See 2.
Apologies as I can't reply to comments. Is it possible to derive the tag from the decrypted data after decryption? This PR associated with PyCA/cryptography seems to imply the exact scenario considered here.
According to the GCM spec (section 7.2: “Algorithm for the
Authenticated Decryption Function”), the tag itself is not needed
until the ciphertext has been decrypted.
Does calling d.update(encrypted_data) decrypt data successfully and d.finalize() is only needed to verify the integrity of the data?
I am generating RSA signature using RSA_PKCS1_PSS_PADDING. I am setting digest algorithm as SHA256 using EVP_get_digestbyname() and EVP_DigestSignInit(). And salt length parameter as -1 using EVP_PKEY_CTX_set_rsa_pss_saltlen().
I have EVP_MD_CTX, EVP_MD and EVP_PKEY_CTX structures used for signature generation.
How can I get the name of Mask generation algorithm name used by OpenSSL by default? Is there any API provided for getting it?
Edit: OpenSSL version used: 1.1.0g.
RSASSA-PSS is in practice always used with MGF1 as the Mask Generation Function. The only variation is which Message Digest is used internally by MGF1.
Sometime that's the same Message Digest as the one used for hashing the message and building the tag in PSS, because that makes the most sense. Other times it is SHA-1 because that used to be the default MD for early RSASSA-PSS APIs, thus for the associated MGF1.
In an ideal world, some attribute (in the signature, or/and in the public key certificate used to check the signature) would tell MGF1-with-such-MD, perhaps by way of some Object IDentifier like we have to specify PSS. But crypto APIs are hell.
In order to control what Message Digest is used by MGF1, we want something on the tune of what -sigopt rsa_mgf1_md:sha256 does in the openssl dgst command.
My best guess is to set the MGF1 digest using
assert(EVP_PKEY_CTX_set_rsa_mgf1_md(ctx, EVP_sha256)>=0);
or get it using EVP_PKEY_CTX_get_rsa_mgf1_md() as documented:
The EVP_PKEY_CTX_get_rsa_mgf1_md() macro gets the MGF1 digest for ctx. If not explicitly set the signing digest is used. The padding mode must have been set to RSA_PKCS1_OAEP_PADDING or RSA_PKCS1_PSS_PADDING.
I'm planning to use code similar to Amazon AWS samples to authenticate signed API requests. So users will have something like:
use Digest::SHA qw(hmac_sha256_base64);
my $digest = hmac_sha256_base64 ($request, $self->{SecretKey});
and attach $digest as a parameter to their request URI. The server-side will use the same algorithm to create a digest from the client URI and compare that to the value sent by the client.
What I can't find is Perl support for generating the SecretKey of the correct length to use when generating HMAC SHA256 digest.
For my Amazon AWS account I'm being given a 40 ASCII character base64 encoded string.
How do I generate a proper secret-key for my clients?
I suggest you use a PBKDF2 algorithm. PBKDF2 = "Password-based Key Derivation Function (#2)". It is defined in PKCS #5 (RFC 2898). This is the recommended way to derive a key from a password. You will need a salt, as well. A typical iteration count is 1000.
This page says it has a perl implementation of PBKDF2. I haven't tried it.
Apparently there is also a Crypto::PBKDF2, but it is saddled with dependencies you may not want.
EDIT
I just tried Anthony Thyssen's perl program for pbkdf2 - it works great. Simple, easy.
We're developing a service that will accept a POST request. Some of the POST data will need to be encrypted before the POST as it will be stored in hidden fields on a form.
The application is written in C#, but we want third party clients to be able to easily integrate with it. We find that most clients use PHP, Classic ASP or VB.Net.
The third parties should only be doing the encryption. We'd do the decryption. There is no two-way communication.
What are the most compatible combinations of encryption algorithm, padding mode and other options?
Assuming that you have a safe way of sharing a key (whether RSA encryption of it, retrieval over an SSH or HTTPS link, or callling the other developer on a secured phone line), any of the major modern encryptions (like AES, as mentioned by #Ed Haber) would be suitable. I would second his suggestion of AES. There should be libraries for PHP, VB, Ruby, etc.
However, remember that with "no two-way communication" you will have to find an out-of-channel method for securely getting the symmetric key to the encrypting party.
If you mean that it should be impossible for third-parties to decrypt data, then you will want to use an asymmetric encryption algorithm such as RSA. This will the third-party to encrypt data with your public key, and then only you can decrypt the data with your private key, which you do not disclose. There should be implementations of RSA available for all the languages you mentioned.
If you don't care if the third-party can decrypt the data, then AES is the way to go. You will have one key which you share with the third-parties. This key is used both for encryption and decryption.
I would use AES for the bulk data encryption and RSA for encrypting the AES Key.
If the data is small enough then just encrypt the whole thing with RSA.
Ed Haber said
I would use AES for the bulk data
encryption and RSA for encrypting the
AES Key. If the data is small enough
then just encrypt the whole thing with
RSA.
I think this is a good solution. What I would do is have your application publish an API for getting a public RSA key. When I third party wants to send you something it gets the public key. It then generates a session key to do the actual encryption using a block cipher, (ie AES), and sends the key to you by encrypting with your public key. You decrypt the session key with your private key. The third party then encrypts the data it wants to send you with AES (using a padding scheme that you also publish) and sends it to you. You decrypt it using the session key.
There are some problems with the method above. Since you are not sending any information (other than publishing your public key, you cannot control how the session key is generated. This means that third parties can use very insecure ways to of generating the session key and you will never know. A second problem is everyone who wants to send you data has to pad data for AES in the same way you do. So you will have to make sure every one co-ordinates. The second issue isn't to big, but the first could be a problem especially if you don't trust the third parties all that much to generate really good session keys from a good cryptographically secure random number generator
You could very easily implement your own XOR key-based bit encryption. With a little thought and ingenuity, you can come up with something that's more than suitable for you application.
Here's a PHP example:
function XOREncryption($InputString, $KeyPhrase){
$KeyPhraseLength = strlen($KeyPhrase);
for ($i = 0; $i < strlen($InputString); $i++){
$rPos = $i % $KeyPhraseLength;
$r = ord($InputString[$i]) ^ ord($KeyPhrase[$rPos]);
$InputString[$i] = chr($r);
}
return $InputString;
}
ColdFusion has the encrypt and decrypt functions capable of handling a range of algorithms and encodings, including the AES recommended above.
Information at: http://www.cfquickdocs.com/cf8/?getDoc=encrypt#Encrypt
Quick example code:
Key = generateSecretKey( 'AES' , 128 )
EncryptedText = encrypt( Text , Key , 'AES' , 'Hex' )
Text = decrypt( EncryptedText , Key, 'AES' , 'Hex' )
Similar functionality is available with this library for PHP:
http://www.chilkatsoft.com/p/php_aes.asp
...and Java, Python, Ruby, and others...
http://www.example-code.com/java/crypt2_aes_matchPhp.asp
http://www.example-code.com/python/aes_stringEncryption.asp
Sounds like RSA is the algorithm for you.
Why not have your server exposed over HTTPS? That way, any client which can handle HTTPS can consume the service securely.