I'm trying to verify a signature generated with Google's cloud KMS, but I keep getting invalid responses.
Here's how I'm testing it:
const versionName = client.cryptoKeyVersionPath(
projectId,
locationId,
keyRingId,
keyId,
versionId
)
const [publicKey] = await client.getPublicKey({
name: versionName,
})
const valueToSign = 'hola, que tal'
const digest = crypto.createHash('sha256').update(valueToSign).digest()
const [signResponse] = await client.asymmetricSign({
name: versionName,
digest: {
sha256: digest,
},
})
const valid = crypto.createVerify('sha256').update(digest).verify(publicKey.pem, signResponse.signature)
if (!valid) return console.log('INVALID SIGNATURE')
console.log('SIGNATURE IS VALID!')
// output: INVALID SIGNATURE
This code will always log 'INVALID SIGNATURE' unless I use the original message instead of its hash:
const valid = crypto.createVerify('sha256').update(valueToSign).verify(publicKey.pem, signResponse.signature) // true
But using a local private key, I'm able to sign messages and verify them using their hashes:
const valueToSign = 'hola, the tal'
const msgHash = crypto.createHash("sha256").update(valueToSign).digest('base64');
const signer = crypto.createSign('sha256');
signer.update(msgHash);
const signature = signer.sign(pk, 'base64');
const verifier = crypto.createVerify('sha256');
verifier.update(msgHash);
const valid = verifier.verify(pubKey, signature, 'base64');
console.log(valid) // true
Why is it? Is there something different about kms signatures?
Based on this example from the crypto module documentation and your observations, I'd say that you might've misunderstood how client.asymmetricSign works. Let's analyze what happens:
Your local private key code:
const valueToSign = 'hola, the tal'
// Create sha256 hash
const msgHash = crypto.createHash("sha256").update(valueToSign).digest('base64');
// Let signer sign sha256(hash)
const signer = crypto.createSign('sha256');
signer.update(msgHash);
const signature = signer.sign(pk, 'base64');
// We now got sign(sha256(hash))
// Let verifier verify sha256(hash)
const verifier = crypto.createVerify('sha256');
verifier.update(msgHash);
const valid = verifier.verify(pubKey, signature, 'base64');
console.log(valid) // true
We are verifying sign(sha256(hash)) using verify(sha256(hash)).
Your KMS code:
const valueToSign = 'hola, que tal'
// Create sha256 hash
const digest = crypto.createHash('sha256').update(valueToSign).digest()
// Let KMS sign the hash
const [signResponse] = await client.asymmetricSign({
name: versionName,
digest: {
sha256: digest, // we already say "we hashed our data using sha256"
},
});
// We now got `sign(hash)`, NOT `sign(sha256(hash))` (where hash == digest)
// Let verifier verify sha256(hash)
const valid = crypto.createVerify('sha256').update(digest).verify(publicKey.pem, signResponse.signature)
We are verifying sign(hash) using verify(sha256(hash)).
Basically, locally you are signing your hash and verifying the signed hash. With KMS you are signing your data and verifying the signed hash, which is actually your signed data, hence your 2nd attempt with .update(valueToSign) works.
Solution? Hash your sha256 hash again before letting KMS sign it, since KMS expects the sha256 hash of the to-be-signed data, while the crypto expects the to-be-signed data (which it'll hash itself given the algorithm you passed to createSign).
The answer is very similar to the one from Kevin but from a different point of view, in other words.
When you use crypto.createSign(<algorithm>) and crypto.createVerify(<algorithm>) you are indicating the digest algorithm that will be used for signature creation and verification, respectively.
When you call update on the returned Sign and Verify objects you need to provide your data as is, crypto will take care of digesting that information as appropriate when you sign or verify it later.
In contrast, the GCP KMS asymmetricSign operation requires a message digest produced with the designated algorithm over your original data as argument. This is why you need to calculate the message digest with crypto.createHash first.
But please, as indicated, be aware that this fact doesn't change the behavior of the crypto verification process, it always requires the original data as input, this is why your code works when you pass your original data without hashing.
Although you provided a working example in your question for reference the GCP documentation provides additional ones.
Related
I am trying to verify a JWT signature in Javascript using a public key. I have tried the 'jose' and 'jsrsasign' libraries with similar results: the verification is painfully slow (400-500ms).
After googling, from what I can understand the actual verification of the signature should be sub-millisecond. Am I doing something wrong or have i misunderstood what people are saying?
This is what the current code looks like using 'jsrsasign':
import rs from 'jsrsasign';
// [...]
const timeBeforeTokenVerify = Date.now();
const keyObject = rs.KEYUTIL.getKey(jwks.keys[0]);
const isValid = rs.jws.JWS.verifyJWT(clientToken, keyObject, { alg: ['RS256'] });
if (!isValid) {
throw new Error('Token is not valid');
}
console.log(Date.now() - timeBeforeTokenVerify) // prints 400 to 500
I'm trying to use javascript to implement this Telegram PHP example, but the hashes don't match. I ran the example's php code; it also failed, and the hash returned by the example and mine are identical. I'm wondering if it has anything to do with my bot settings (I set my domain to 127.0.0.1 and try another https domain - still failed). I'm completely stuck and have no idea how to make it work.
My code is as follows:
'use strict';
const sha256 = require('crypto-js/sha256');
const hmacSHA256 = require('crypto-js/hmac-sha256');
const Hex = require('crypto-js/enc-hex');
const { ValidationError } = require('objection');
const data = {
id: 5528016998,
first_name: 'Lê Văn',
last_name: 'Hùng',
username: 'HungLV46',
photo_url: 'https://t.me/i/userpic/320/gy_L5Q2zLLZyUrtCw0Fh9D0oy0CuabiM2A0-68FY27Inz94yP7ypxCrAF7G7_asy.jpg',
auth_date: 1670774122,
hash: '893e4bd66128d8696d63aa285b0aff6e8b970cbe9c9e43dacfa9e2da7420c16c'
};
function verifyAuthorization(params) {
if ((new Date() - new Date(params.auth_date * 1000)) > 86400000) { // milisecond
throw new ValidationError('Authorization data is outdated');
}
const verificationParams = { ...params };
delete verificationParams.hash;
const message = Object.keys(verificationParams)
.map(key => `${key}=${verificationParams[key]}`)
.sort().join('\n');
const secretKey = sha256('xxxx'); // replace with the token of my bot
const hash = Hex.stringify(hmacSHA256(message, secretKey));
if (hash !== params.hash) {
throw new ValidationError('Authorization data is not from telegram!');
}
}
verifyAuthorization(data);
similar question
Edit:
After experimenting with the login widget for some time, I tried to print return value to the developer console of Chrome (pressing f12, console tab), and then run my code again. The 2 hashes awardly match!.
I initially got the user info and hash from the response of this API, POST https://oauth.telegram.org/auth/get?bot_id=xxx in network-tab, and it turned out the response of API in network-tab was different from the response printed in console-tab.
I tried some more time, and the response of the API suddenly worked well with my function. Now I am just curious about why?
I am trying to create a token collection using the Aptos Typescript SDK.
const account = new AptosAccount(Uint8Array.from(Buffer.from(PRIVATE_KEY)), ACCOUNT_ADDR);
await tokenClient.createCollection(
account,
"A test collection 1",
"A test collection",
"https://google.com",
);
But I get the following error:
ApiError2: {"message":"Invalid transaction: Type: Validation Code: INVALID_AUTH_KEY","error_code":"vm_error","vm_error_code":2}
What am I doing wrong?
Tried replicating the Aptos official example but instead of creating a new account, I want to use an existing funded account.
Let's say you have a private key as a hex string, you can do it like this:
import { AptosAccount, HexString } from "aptos";
const privateKeyHex = "0xdcaf65ead38f7cf0eb4f81961f8fc7f9b7f1e2f45e2d4a6da0dbef85f46f6057";
const privateKeyBytes = HexString.ensure(privateKeyHex).toUint8Array();
const account = new AptosAccount(privateKeyBytes);
I had the same issue. I Finally found out that the problem is the address that is not match with private_key, and the private_key was not in ed25519 format.
Your keys must be generated with ed25519 curve, then you should create your address from that key. I used bip_utils library to create bip_private_key (with Near protocol which is also ed25519)then:
private_key = ed25519.PrivateKey.from_hex(bip_private_key)
I just want to add details to Daniel's answer about how you can get private key after creating wallet and then use it:
import { AptosAccount } from "aptos";
const wallet = new AptosAccount();
const privateKeyHex = wallet.toPrivateKeyObject().privateKeyHex;
// ...
const privateKeyBytes = HexString.ensure(privateKeyHex).toUint8Array();
const account = new AptosAccount(privateKeyBytes);
Trying to do a simple send and receive function in Solana with vanilla JS.
Below is my send function that works fine, and now I want a receive function.
Where the provider would get Solana transferred from my treasury wallet.
I'm not sure what approach I should have, just starting out. Is there a way to just move things around in this function? Or do I have to have a totally different approach?
Thanks!
async function transferSOL() {
// Detecing and storing the phantom wallet of the user (creator in this case)
var provider = phantom;
// Establishing connection
var connection = new web3.Connection(
web3.clusterApiUrl('devnet'),
);
var transaction = new web3.Transaction().add(
web3.SystemProgram.transfer({
fromPubkey: provider.publicKey,
toPubkey: treasuryWallet.publicKey,
lamports: 0.1 * web3.LAMPORTS_PER_SOL - 100
}),
);
// Setting the variables for the transaction
transaction.feePayer = await provider.publicKey;
let blockhashObj = await connection.getRecentBlockhash();
transaction.recentBlockhash = await blockhashObj.blockhash;
// Request creator to sign the transaction (allow the transaction)
let signed = await provider.signTransaction(transaction);
let signature = await connection.sendRawTransaction(signed.serialize());
await connection.confirmTransaction(signature);
}
If you want to transfer from your treasury to the user, then you must sign for treasuryWallet somehow. In your case, it looks like you already have the keypair available to you in your app, so you can simply do:
var transaction = new web3.Transaction().add(
web3.SystemProgram.transfer({
toPubkey: provider.publicKey,
fromPubkey: treasuryWallet.publicKey,
lamports: 0.1 * web3.LAMPORTS_PER_SOL - 100
}),
);
// Setting the variables for the transaction
transaction.feePayer = treasuryWallet.publicKey;
let blockhashObj = await connection.getRecentBlockhash();
transaction.recentBlockhash = await blockhashObj.blockhash;
// Request creator to sign the transaction (allow the transaction)
transaction.sign(treasuryWallet);
let signature = await connection.sendRawTransaction(transaction.serialize());
await connection.confirmTransaction(signature);
Note that this is very unsafe! Everyone who uses your web app has full access to the treasury funds if the keypair is exposed, since they can just sign all the transactions that they want with it.
I'm developing an application that uses Twillios Programmable Video API.
I'm new to using Node JS, but the documentation has been fairly straightforward, however I still have a few questions.
Here's code I am referencing.
const AccessToken = require('twilio').jwt.AccessToken;
const VideoGrant = AccessToken.VideoGrant;
// Used when generating any kind of tokens
const twilioAccountSid = 'ACxxxxxxxxxx';
const twilioApiKey = 'SKxxxxxxxxxx';
const twilioApiSecret = 'xxxxxxxxxxxx';
const identity = 'user';
// Create Video Grant
const videoGrant = new VideoGrant({
room: 'cool room'
});
// Create an access token which we will sign and return to the client,
// containing the grant we just created
const token = new AccessToken(twilioAccountSid, twilioApiKey, twilioApiSecret);
token.addGrant(videoGrant);
token.identity = identity;
// Serialize the token to a JWT string
console.log(token.toJwt());
In this specific example provided by Twillio, the video grant which I assume is mandatory is explicitly referenced, however that would mean for this specific token generator, the users can only enter rooms of that name.
I was wondering if it was possible to reference the room before configuring the token. Something similar to how the identity is a variable that's entered into the function before the token is output.
In addition, are there any required dependencies or libraries when creating tokens outside of Twillios own function environment?
Any answers, suggestions, or references are greatly appreciated.
Twilio developer evangelist here.
It is possible to supply the room name as a variable too. You might want to create a function that can take an identity and room name as arguments and returns an access token. Something like this:
const AccessToken = require('twilio').jwt.AccessToken;
const VideoGrant = AccessToken.VideoGrant;
// Used when generating any kind of tokens
const twilioAccountSid = 'ACxxxxxxxxxx';
const twilioApiKey = 'SKxxxxxxxxxx';
const twilioApiSecret = 'xxxxxxxxxxxx';
function generateToken(identity, roomName) {
const videoGrant = new VideoGrant({
room: roomName
});
const token = new AccessToken(twilioAccountSid, twilioApiKey, twilioApiSecret);
token.addGrant(videoGrant);
token.identity = identity;
return token.toJwt();
}
Then you can use the function like:
const token = generateToken("Stefan", "StefansRoom");
Let me know if that helps at all.