I've been struggling to convert the code from Python to Swift and can't get the same signature value. I've been trying different combinations in Swift but my requests are failing as the signature is incorrect. I've tried looking at other HMAC posts in swift but haven't had much luck.
Python code:
hashed = hmac.new(base64.urlsafe_b64decode(
(secretMessage_string).encode('utf-8'),
),
msg=message_string.encode('utf-8'),
digestmod=hashlib.sha256,
)
signature = base64.urlsafe_b64encode(hashed.digest()).decode()
Swift code:
let keyString = "specialKey"
let messageString = "This is a basic message"
let key = SymmetricKey(data: Data(keyString.utf8))
let signature = HMAC<SHA256>.authenticationCode(for: Data(messageString.utf8), using: key)
Try this:
import CryptoKit
let keyString = "specialKey"
let keyData = Data(base64Encoded: keyString)!
let messageString = "This is a basic message"
let key = SymmetricKey(data: keyData)
let signatureObj = HMAC<SHA256>.authenticationCode(for: Data(messageString.utf8), using: key)
let signatureHex = Data(signatureObj).map { String(format: "%02hhx", $0) }.joined()
let signature = Data(signatureHex.utf8).base64EncodedString()
Related
We are trying to verify that the SurveyMonkey hmac we receive (sm-signature) is correct. To check this we create an hmac and compare it to SurveyMonkey's hmac.
We create the hmac as follows (we are working with nodejs):
let bodyString = JSON.stringify(req.body);
let body = Buffer.from(bodyString, "ascii");
let apiClientId = Buffer.from(surveyMonkeyClientId, "ascii");
let apiSecret = Buffer.from(surveyMonkeyApiSecret, "ascii");
let hmac = crypto
.createHmac('sha1', apiClientId+'&'+apiSecret)
.update(Buffer.from(body))
.digest()
.toString('base64');
We have verified this code with (it is with python): https://github.com/SurveyMonkey/public_api_docs/blob/main/includes/_webhooks.md
But for some reason this doesn't work as expected. Because the hmac we generated is not the same as the hmac generated by SurveyMonkey (sm-signature).
Could someone help us? Thanks!
The problem is the signature, that comes with spaces between the json fields and when you do JSON.stringify this removes the spaces.
One possible solution is:
let payloadString = JSON.stringify(req.body);
payloadString = payloadString.replace(/":/g, '": ');
payloadString = payloadString.replace(/,/g, ', ');
This question is the inverse of the existing one here:
Encrypt in python 3.7 and decode in NODEJS 12 .
I would prefer to use the exact equivalent of tweet-nacl on python but that project says it is old and not recommended https://github.com/warner/python-tweetnacl . Their recommended replacement is https://github.com/pyca/pynacl : but that one is an interface to libsodium not tweet-nacl and there is no clear documentation on how to achieve the decryption.
Here is the JS encryption:
let msgArr = naclutil.decodeUTF8(jprint(msg))
let nonce = nacl.randomBytes(nacl.box.nonceLength)
let keyPair = this.genKeyPair()
let encrypted = nacl.box(
msgArr,
nonce,
naclutil.decodeBase64(pubKey),
naclutil.decodeBase64(keyPair.privkey)
)
let nonce64 = naclutil.encodeBase64(nonce)
let encrypted64 = naclutil.encodeBase64(encrypted)
The (working) tweet-nacl javascript decryption code is:
const decryptedMessage = nacl.box.open(
naclutil.decodeBase64(payload.encrypted.encrypted),
naclutil.decodeBase64(payload.encrypted.nonce),
naclutil.decodeBase64(payload.encrypted.ephemPubKey),
naclutil.decodeBase64(privKey)
)
const decodedMessage = naclutil.encodeUTF8(decryptedMessage)
My problem is that for pynacl they do not show any examples of using the ephemPubKey for decryption. The examples I could find were like the following:
import binascii
from nacl.encoding import HexEncoder
from nacl.exceptions import CryptoError
from nacl.secret import Aead, SecretBox
benc= binascii.unhexlify(encrypted)
bnonce = binascii.unhexlify(nonce)
box = SecretBox(privKey, encoder=HexEncoder)
decrypted = box.decrypt(benc, bnonce, encoder=HexEncoder),
Has anyone been able to get the tweet-nacl Javascript generated encryption successfully decrypted into python?
SecretBox and thus the PyNaCl example you posted is for symmetric encryption, s. here. You can find the documentation for public key encryption with PyNaCl here and for PyNaCl in general here.
In the following example, a plaintext is encrypted with TweetNaCl.js and decrypted with PyNaCl.
JavaScript side - Encryption with TweetNaCl.js:
var secretKey_js = nacl.util.decodeBase64("FJGsHP0dMkDNkpAkT4hZrcbv27L8XNO8ymhLxpPpDkE=");
var publicKey_py = nacl.util.decodeBase64("0EyrzGW6qn0EGEV0Cx2Z7tQeln6FdwZVINz0FezlvTM=");
var nonce = nacl.randomBytes(24)
var msgStr = "The quick brown fox jumps over the lazy dog";
var message = nacl.util.decodeUTF8(msgStr);
var box_js = nacl.box(message, nonce, publicKey_py, secretKey_js)
console.log(nacl.util.encodeBase64(nonce)) // 2e8WuEr0+5nc14VBxQrOl4ob6guOTySr
console.log(nacl.util.encodeBase64(box_js)) // eJ8sO0mFNaaWLeXVcNNpw0PurwfINp/BlnErSzOnxXJ5zqu3wLrW4fHIa4kIAxFkuMVJaf0AR4pYon0=
The code is basically the same as your code with the difference that the keys are not generated but imported.
Python side - Decryption with PyNaCl:
import base64
from nacl.public import PrivateKey, PublicKey, Box
from nacl.encoding import Base64Encoder
secretKeyB64_py = "XVdFnozXd+7xm6MVazPemgSq6un+fGpDvwgxo9UbsdM=";
publicKeyB64_js = "ixxgLis2RzqMWys76HuoH7TwrwBbXoDrwl3jGsRysRI=";
secretKey_py = PrivateKey(secretKeyB64_py, encoder=Base64Encoder)
publicKey_js = PublicKey(publicKeyB64_js, encoder=Base64Encoder)
nonce = base64.b64decode("2e8WuEr0+5nc14VBxQrOl4ob6guOTySr");
box_js = base64.b64decode("eJ8sO0mFNaaWLeXVcNNpw0PurwfINp/BlnErSzOnxXJ5zqu3wLrW4fHIa4kIAxFkuMVJaf0AR4pYon0=");
box_py = Box(secretKey_py, publicKey_js)
data = box_py.decrypt(nonce + box_js)
print(data) # b'The quick brown fox jumps over the lazy dog'
In the example above, the keys have been imported. If a key pair needs to be generated, this is done with PyNaCl as follows:
from nacl.public import PrivateKey
secretKeyNew = PrivateKey.generate()
publicKeyNew = secretKeyNew.public_key
About compatibility:
TweetNaCl.js and PyNaCl are compatible. As you described, although PyNaCl is a wrapper of Libsodium for Python (s. here and here), but Libsodium itself is a port of NaCl (s. here), as is TweetNacl.js for JavaScript (here).
So ultimately, both TweetNaCl and PyNaCl are based on NaCl, the original library implemented by Bernstein et al., s. here, and are therefore compatible (except perhaps for a few minor syntactical differences).
I have a code in PHP given below:
base64_encode(hash_hmac('sha256', $message, $secret, true));
Now i want the same functionality in Python. Since i am a beginner with Python, please help me doing this.
php code.
$s=base64_encode(hash_hmac('sha256', "Your message", 'secret', true));
echo $s;
Output: xjyYPoGLbqdttWiTL7WPt6F5E9zl+3hjVaPftU/jvj8=
Python code.
import base64
import hashlib
import hmac
message = "Your message"
hashValue = hmac.new(b"secret", message.encode('utf-8'), digestmod=hashlib.sha256).digest()
base64Value = base64.b64encode(hashValue).decode()
print(base64Value)
Output: xjyYPoGLbqdttWiTL7WPt6F5E9zl+3hjVaPftU/jvj8=
Result: Both output are same.
Help: https://www.jokecamp.com/blog/examples-of-creating-base64-hashes-using-hmac-sha256-in-different-languages/
So, i wrote this code:
json_data = "{\"photoBytes\":" + str(bytearray(open("image.png", "rb").read())) + "}"
service.people().updateContactPhoto('people/c4942919248052589775', json_data)
but it gives me this error:
TypeError: method() takes 1 positional argument but 3 were given
I cannot understand why. As the docs say, I should use it like this...
You are passing a string value as a request body which should be in json format.
Try using json.dumps() first on your json_date before passing it as a parameter in updateContactPhoto().
Your code should look like this:
json_data = "{\"photoBytes\":" + str(bytearray(open("image.png", "rb").read())) + "}"
service.people().updateContactPhoto('people/c4942919248052589775', json.dumps(json_data))
I was able to figure this out with a lot of trial and error and I found that the photo needs to be base64 encoded and the best method to get that into JSON below (thanks to this answer for this tip).
Here's the working code for me below
import base64
JSON = """{
"photoBytes": "%s"
}
""" % (base64.b64encode(open("test.png", "rb").read()).decode('utf-8'))
update_body = json.loads(JSON)
service.people().updateContactPhoto(resourceName="people/123456789", body=update_body).execute()
I have been trying for a few days to validate some message signed with a private key in python. Note that the message has been signed using Ruby.
When I sign the same message in python I can verify it no problem. Note that I have already validated that the hash are the same.
Python code:
string_to_encrypt = b"aaaaabbbbbaaaaabbbbbaaaaabbbbbCC"
sha1 = SHA.new()
sha1.update(string_to_encrypt)
# load private key
pkey = OpenSSL.crypto.load_privatekey(OpenSSL.crypto.FILETYPE_PEM, open('./license.pem', 'rb').read())
sign_ssl = OpenSSL.crypto.sign(pkey, sha1.digest(), 'RSA-SHA1')
b64_ssl = base64.b64encode(sign_ssl)
Ruby:
string_to_encrypt = "aaaaabbbbbaaaaabbbbbaaaaabbbbbCC"
sha1 = Digest::SHA1.digest(string_to_encrypt)
#sign it
private_key_file = File.join(File.dirname(__FILE__), 'license.pem')
rsa = OpenSSL::PKey::RSA.new(File.read(private_key_file))
signed_key = rsa.private_encrypt(sha1)
#update the license string with it
x = Base64.strict_encode64(signed_key)
I would expect b64_ssl and x to contain the same value and they don't. Could someone explain to me what I missing there?
Neither of these code snippets is actually producing the correct signature.
In the Ruby OpenSSL library you want to be using the sign method, not the private_encrypt method, which is a low level operation that doesn’t do everything required to produce a valid signature.
In both libraries the sign operation performs the hashing for you, you don’t need to do this beforehand. In fact your Python code is actually hashing the data twice.
Try the following Python code:
import OpenSSL
import base64
string_to_encrypt = b"aaaaabbbbbaaaaabbbbbaaaaabbbbbCC"
# load private key
pkey = OpenSSL.crypto.load_privatekey(OpenSSL.crypto.FILETYPE_PEM, open('./license.pem', 'rb').read())
sign_ssl = OpenSSL.crypto.sign(pkey, string_to_encrypt, 'SHA1')
b64_ssl = base64.b64encode(sign_ssl)
print(b64_ssl.decode())
which produces the same output as this Ruby code:
require 'openssl'
require 'base64'
string_to_encrypt = "aaaaabbbbbaaaaabbbbbaaaaabbbbbCC"
#sign it
private_key_file = File.join(File.dirname(__FILE__), 'license.pem')
rsa = OpenSSL::PKey::RSA.new(File.read(private_key_file))
signed_key = rsa.sign('sha1', string_to_encrypt)
#update the license string with it
x = Base64.strict_encode64(signed_key)
puts x