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, ', ');
Related
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()
I am using RabbitMQ with Docker. I would like to update the configurations directly in the definitions.json file. The users should have their password stored there with rabbit_password_hashing_sha256 hashing algorithm. I have found a useful Python script for hashing the password but I was not able to reproduce it's logic in NodeJS with Crypto library.
Python script:
#!/usr/bin/env python3
# RabbitMQ password hashing algorith as laid out in:
# http://lists.rabbitmq.com/pipermail/rabbitmq-discuss/2011-May/012765.html
from __future__ import print_function
import base64
import os
import hashlib
import struct
import sys
# The plain password to encode
password = sys.argv[1]
# Generate a random 32 bit salt
salt = os.urandom(4)
# Concatenate with the UTF-8 representation of plaintext password
tmp0 = salt + password.encode('utf-8')
# Take the SHA256 hash and get the bytes back
tmp1 = hashlib.sha256(tmp0).digest()
# Concatenate the salt again
salted_hash = salt + tmp1
# Convert to base64 encoding
pass_hash = base64.b64encode(salted_hash)
# Print to the console (stdout)
print(pass_hash.decode("utf-8"))
Output: python hash-password.py test >> t7+JG/ovWbTd9lfrYrPXdFhNZLcO+y56x4z0d8S2OutE6XTE
First implementation failure:
const crypto = require('crypto');
this.password = process.argv[2];
this.salt = crypto.randomBytes(16).toString('hex');
this.password_hash = crypto.pbkdf2Sync(this.password.trim(), this.salt, 1000, 24, `sha256`).toString(`hex`);
console.log(this.password_hash);
Output: node password.js test >> 7611058fb147f5e7a0faab8a806f56f047c1a091d8355544
I was not able to reproduce it in NodeJS, so I collected the stdout result of the executed child process, which is not too elegant.
Second implementation failure:
const crypto = require('crypto');
const utf8 = require('utf8');
this.password = process.argv[2];
this.salt = crypto.randomBytes(4);
this.tmp0 = this.salt + utf8.encode(this.password);
this.tmp1 = crypto.createHash(`sha256`).digest();
this.salted_hash = this.salt + this.tmp1;
this.pass_hash = Buffer.from(this.salted_hash).toString('base64');
console.log(utf8.decode(this.pass_hash));
Output: node password.js test >> Mu+/ve+/vWnvv73vv71C77+977+9HBTvv73vv73vv73ImW/vv70kJ++/vUHvv71k77+977+9TO+/ve+/ve+/vRt4Uu+/vVU=
Can anyone help with the right implementation?
You can do the port to NodeJS more or less 1:1:
var crypto = require('crypto')
// The plain password to encode
var password = Buffer.from('my passphrase', 'utf8') // sample password
// Generate a random 32 bit salt
var salt = crypto.randomBytes(4);
//var salt = Buffer.from('1234', 'utf8'); // for testing, gives pass_hash = MTIzNNcAIpZVAOz2It9VMePU/k4wequLpsQVl+aYDdJa6y9r
// Concatenate with the UTF-8 representation of plaintext password
var tmp0 = Buffer.concat([salt, password])
// Take the SHA256 hash and get the bytes back
var tmp1 = crypto.createHash('sha256').update(tmp0).digest()
// Concatenate the salt again
var salted_hash = Buffer.concat([salt, tmp1])
// Convert to base64 encoding
pass_hash = salted_hash.toString('base64')
// Print to the console (stdout)
console.log(pass_hash)
The code above uses as example password my passphrase. You need to replace the password with yours.
Note that even if the passwords are identical, you cannot directly compare the results of Python and NodeJS code because of the random salt.
Therefore, the commented out line with the UTF-8 encoded salt 1234 can be used to produce a result for comparison with the Python code: MTIzNNcAIpZVAOz2It9VMePU/k4wequLpsQVl+aYDdJa6y9r
The issue in your first implementation is, among other things, the use of PBKDF2, which is not applied in the Python code.
The second implementation is closer to the actual solution. One problem is that the hashing does not take into account the data to be hashed.
Another defect is the use of strings, where some operations implicitly apply UTF-8 encoding, which corrupts the (arbitrary binary) data. To prevent this, binary must be used as encoding instead of UTF-8. Just because of the possible encoding issues, the implementation with strings is less robust here than with buffers.
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 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
I’m implementing a service in Python that interacts with Magento through SOAP v2. So far, I’m able to get the product list doing something like this:
import suds
from suds.client import Client
wsdl_file = 'http://server/api/v2_soap?wsdl=1'
user = 'user'
password = 'password'
client = Client(wsdl_file) # load the wsdl file
session = client.service.login(user, password) # login and create a session
client.service.catalogProductList(session)
However, I’m not able to create a product, as I don’t really know what data I should send and how to send it. I know the method I have to use is catalogProductCreate, but the PHP examples shown here don’t really help me.
Any input would be appreciated.
Thank you in advance.
You are there already. I think your issue is not able to translate the PHP array of arguments to be passed into Python Key Value pairs. If thats the case this will help you a bit. As well as need to set the attribute set using catalogProductAttributeSetList before you create the product
attributeSets = client.service.catalogProductAttributeSetList(session)
#print attributeSets
# Not very sure how to get the current element from the attributeSets array. hope the below code will work
attributeSet = attributeSets[0]
product_details = [{'name':'Your Product Name'},{'description':'Product description'},{'short_description':'Product short description'},{'weight':'10'},{ 'status':'1'},{'url_key':'product-url-key'},{'url_path':'product-url-path'},{'visibility' :4},{'price':100},{'tax_class_id':1},{'categories': [2]},{'websites': [1]}]
client.service.catalogProductCreate(session , 'simple', attributeSet.set_id, 'your_product_sku',product_details)
suds.TypeNotFound: Type not found: 'productData'
Because the productData format is not right. You may want to change from
product_details = [{'name':'Your Product Name'},{'description':'Product description'}, {'short_description':'Product short description'},{'weight':'10'},{ 'status':'1'},{'url_key':'product-url-key'},{'url_path':'product-url-path'},{'visibility' :4},{'price':100},{'tax_class_id':1},{'categories': [2]},{'websites': [1]}]
to
product_details = {'name':'Your Product Name','description':'Product description', 'short_description':'Product short description','weight':'10', 'status':'1','url_key':'product-url-key','url_path':'product-url-path','visibility' :4,'price':100,'tax_class_id':1,'categories': [2],'websites': [1]}
It works for me.