I have a problem with crypto-js vs python sha256. It would like to write a nodejs client for duplicati. So I tried to port some python code to js.
https://github.com/Pectojin/duplicati-client/blob/master/auth.py#L136
Javascript
const CryptoJS = require('crypto-js');
function sha256(to_sign) {
var hash = CryptoJS.SHA256(to_sign.toString());
var hashInBase64 = CryptoJS.enc.Base64.stringify(hash);
return hashInBase64.toString('utf-8');
}
let salt = "ZAwQqEAAwR78oZOxFu0nVH2FLy/BnulVxhuu9IOnBwg="
let salt2 = "YQ=="
let password = "abc"
let saltedpwd = sha256(Buffer.concat([Buffer.from(password),Buffer.from(salt,'base64')]));
let saltedpwd2 = sha256(Buffer.concat([Buffer.from(password),Buffer.from(salt2,'base64')]));
let new_password = saltedpwd.toString('base64');
let new_password2 = saltedpwd2.toString('base64');
console.log(new_password)
console.log(new_password2)
returns:
pw1: 0udYFffMXd2QWW9dVXbFl3qp/6lnRcnspr4M1VEtgJA=
pw2: XD9nt/qE374RGDh8rRR5OSmEWlvHwAgMTYMJ03uqaNA=
Python
import base64
import hashlib
import sys
password = "abc"
salt = "ZAwQqEAAwR78oZOxFu0nVH2FLy/BnulVxhuu9IOnBwg="
salt2 = "YQ=="
salt_password = password.encode() + base64.b64decode(salt)
saltedpwd = hashlib.sha256(salt_password).digest()
print (base64.b64encode(saltedpwd).decode('utf-8'))
salt_password2 = password.encode() + base64.b64decode(salt2)
saltedpwd2 = hashlib.sha256(salt_password2).digest()
print (base64.b64encode(saltedpwd2).decode('utf-8'))
returns:
pw1: v9bAzxPatGzA2W7ORkraUvh+nyXotWXItAKpawGSo+A=
pw2: XD9nt/qE374RGDh8rRR5OSmEWlvHwAgMTYMJ03uqaNA=
as you can see, pw2 with the very simple base64 salt is both the same.
the salt from pw1 comes from the duplicati server, so I can't control it...
I've tried some many combinations of encoding, CryptoJS options, so I'm at the point where I will soon stop my 'project'... :(
Can you please give me any advice, what I'm doing wrong? I would be glad about any information.
Regards,
Benjamin
You're 99% of the way there, I think the fix is a one liner (isn't it so often!).
We just have to change
var hash = CryptoJS.SHA256(to_sign.toString());
to
var hash = CryptoJS.SHA256(CryptoJS.lib.WordArray.create(to_sign));
I believe this is because we want to convert directly from the buffer we created by concatenating the password and salt, rather than converting to a string, which is causing the wrong hash to be computed.
In any case, we get the same output as the Python code, which is what we want, ie
v9bAzxPatGzA2W7ORkraUvh+nyXotWXItAKpawGSo+A=
XD9nt/qE374RGDh8rRR5OSmEWlvHwAgMTYMJ03uqaNA=
The new code would look like this in Node.js:
const CryptoJS = require('crypto-js');
function sha256(to_sign) {
var hash = CryptoJS.SHA256(CryptoJS.lib.WordArray.create(to_sign));
var hashInBase64 = CryptoJS.enc.Base64.stringify(hash);
return hashInBase64.toString('utf-8');
}
let salt = "ZAwQqEAAwR78oZOxFu0nVH2FLy/BnulVxhuu9IOnBwg="
let salt2 = "YQ=="
let password = "abc"
let saltedpwd = sha256(Buffer.concat([Buffer.from(password),Buffer.from(salt,'base64')]));
let saltedpwd2 = sha256(Buffer.concat([Buffer.from(password),Buffer.from(salt2,'base64')]));
let new_password = saltedpwd.toString('base64');
let new_password2 = saltedpwd2.toString('base64');
console.log(new_password)
console.log(new_password2)
Related
I need to have a python code and a swift code exchange encrypted message.
Here's what I tried:
Fernet
After a review of the options, I thought that a symetric key algorithm could work well.
In python (as usual), it is straightforward to encrypt and decrypt:
Fernet(key).encrypt(b"mdg") # encrypt
Fernet(key).decrypt(encryptedMsg) # decrypt
In swift, it seemed initially straightforward with something along the lines of:
func encrypt(key: String, msg: String) throws -> String {
let data = Data(base64URL: key)!
let symetricKey = try! SymmetricKey(data: d)
let msgUtf8 = msg.data(using: .utf8)!
let sealBox = try! AES.GCM.seal(msgUtf8, using: symetricKey, nonce: nil)
return sealBox.combined.base64EncodedString();
}
However, I have been unable to find the algorithm in swift matching python's Fernet.
ChaCha
While searching for the problem, I landed on this amazing answer from Bram. Very unfortunately it only solves one side of my problem : encrypting messages in python and decoding them in swift. I also need the reverse process.
How to solve this?
To start, we first need a way to create secure random values to generate the IV and keys. You can also generate the keys using CryptoKit's SymmetricKey and extract the data from them, but for now, I'll use this function.
extension Data {
static func secureRandom(ofSize size: Int) -> Data {
var output = [UInt8](repeating: 0, count: size)
_ = SecRandomCopyBytes(kSecRandomDefault, size, &output)
return Data(output)
}
}
We then require the possibility to compute the AES CBC ciphertext, which can be done using CommonCrypto.
func encrypt(plaintext: Data, key: Data, iv: Data) -> Data {
var encryptor: CCCryptorRef?
defer {
CCCryptorRelease(encryptor)
}
var key = Array(key)
var iv = Array(iv)
var plaintext = Array(plaintext)
CCCryptorCreate(CCOperation(kCCEncrypt), CCAlgorithm(kCCAlgorithmAES), CCOperation(kCCOptionPKCS7Padding), &key, key.count, &iv, &encryptor)
var outputBytes = [UInt8](repeating: 0, count: CCCryptorGetOutputLength(encryptor, plaintext.count, false))
CCCryptorUpdate(encryptor, &plaintext, plaintext.count, &outputBytes, outputBytes.count, nil)
var movedBytes = 0
var finalBytes = [UInt8](repeating: 0, count: CCCryptorGetOutputLength(encryptor, 0, true))
CCCryptorFinal(encryptor, &finalBytes, finalBytes.count, &movedBytes)
return Data(outputBytes + finalBytes[0 ..< movedBytes])
}
and the HMAC with the SHA-256 hash function. I recommend using CryptoKit's HMAC implementation here, but to keep things simple, I went with the CommonCrypto implementation.
func computeHMAC(_ data: Data, using key: Data) -> Data {
var data = Array(data)
var key = Array(key)
var macOut = [UInt8](repeating: 0, count: Int(CC_SHA256_DIGEST_LENGTH))
CCHmac(CCHmacAlgorithm(kCCHmacAlgSHA256), &key, key.count, &data, data.count, &macOut)
return Data(macOut)
}
This brings all of this together into the following
let plaintext = Data("Hello world!".utf8)
let signingKey = Data.secureRandom(ofSize: kCCKeySizeAES128)
let cryptoKey = Data.secureRandom(ofSize: kCCKeySizeAES128)
let fernetKey = (signingKey + cryptoKey).base64EncodedString()
let version: [UInt8] = [0x80]
let timestamp: [UInt8] = {
let timestamp = Int(Date().timeIntervalSince1970).bigEndian
return withUnsafeBytes(of: timestamp, Array.init)
}()
let iv = Data.secureRandom(ofSize: kCCBlockSizeAES128)
let ciphertext = encrypt(plaintext: plaintext, key: cryptoKey, iv: iv)
let hmac = computeHMAC(version + timestamp + iv + ciphertext, using: signingKey)
let fernetToken = (version + timestamp + iv + ciphertext + hmac).base64EncodedString()
print("Fernet key: \(fernetKey)")
print("Fernet token: \(fernetToken)")
An example output can be
Fernet key: 7EwFlYNKTGfj+2fSgL3AUqtrRqRs4D1TWNK7t2XbGJQ=
Fernet token: gAAAAABivCLM0y0poDtGOohT1yK4XTDJppYPJdu4fuDTZ5tb9P9KP5ACgX8aJq4imsSdbzOCcvY3Tueo4FYbwyG+ZugozILL+Q==
We can use this in python using cryptography.io's implementation
from cryptography.fernet import Fernet
key = b'7EwFlYNKTGfj+2fSgL3AUqtrRqRs4D1TWNK7t2XbGJQ='
token = b'gAAAAABivCLM0y0poDtGOohT1yK4XTDJppYPJdu4fuDTZ5tb9P9KP5ACgX8aJq4imsSdbzOCcvY3Tueo4FYbwyG+ZugozILL+Q=='
Fernet(key).decrypt(token)
# b'Hello world!'
I use the same protocol files, but I find that they have different output in Python and C++.
My protocol file:
namespace serial.proto.api.login;
table LoginReq {
account:string; //账号
passwd:string; //密码
device:string; //设备信息
token:string;
}
table LoginRsp {
account:string; //账号
passwd:string; //密码
device:string; //设备信息
token:string;
}
table LogoutReq {
account:string;
}
table LogoutRsp {
account:string;
}
My python code:
builder = flatbuffers.Builder()
account = builder.CreateString('test')
paswd = builder.CreateString('test')
device = builder.CreateString('test')
token = builder.CreateString('test')
LoginReq.LoginReqStart(builder)
LoginReq.LoginReqAddPasswd(builder, paswd)
LoginReq.LoginReqAddToken(builder, token)
LoginReq.LoginReqAddDevice(builder, device)
LoginReq.LoginReqAddAccount(builder, account)
login = LoginReq.LoginReqEnd(builder)
builder.Finish(login)
buf = builder.Output()
print(buf)
with open("layer.bin1","wb") as f:
f.write(buf)
My C++ code:
flatbuffers::FlatBufferBuilder builder;
auto account = builder.CreateString("test");
auto device = builder.CreateString("test");
auto passwd = builder.CreateString("test");
auto token = builder.CreateString("test");
auto l = CreateLoginReq(builder, account = account, passwd = passwd, device = device, token = token);
builder.Finish(l);
auto buf = builder.GetBufferPointer();
flatbuffers::SaveFile("layer.bin", reinterpret_cast<char *>(buf), builder.GetSize(), true);
output:
md5 layer.bin
MD5 (layer.bin) = 496e5031dda0f754fb4462fadce9e975
Flatbuffers generated by different implementations (i.e. generators) don't necessarily have the same binary layout, but can still be equivalent. It depends on how the implementation decide to write out the contents. So taking the hash of the binary is not going to tell you equivalence.
let message = {id : 1, metadata : "abc"}
let signature = <signature>
let nonce = "\x19Ethereum Signed Message:\n" + JSON.stringify(message).length + JSON.stringify(message)
nonce = util.keccak(Buffer.from(nonce, "utf-8"))
const { v, r, s } = util.fromRpcSig(signature)
const pubKey = util.ecrecover(util.toBuffer(nonce), v, r, s)
const addrBuf = util.pubToAddress(pubKey)
const addr = util.bufferToHex(addrBuf)
Hello guys, I am using python "eth-utils" to replicate the code displayed above, however I have no idea how to do it, first, the formatting of JSON.stringify() in javascript might be different from python json.dumps() one, I am wondering if there are equivalent functions in "eth-utils" to keccak(), fromRpcSig(), erecover() and pubToAddress() in the javascript version. If there are no such functions, are there some ways to accomplish the same things?
Finally I found a solution which utilizes eth_account package in python:
message_hash = eth_account.messages.encode_defunct(text=json.dumps(messageSession, separators=(',', ':')))
recoveredAddress = eth_account.Account.recover_message(message_hash, signature=signature)
and It does the exact same thing as mentioned in the question
<?php
$password = "SECRETPASSWORD";
$nonce = random_bytes(32); # requires PHP 7
date_default_timezone_set("UTC");
$timestamp = date(DATE_ATOM);
$encodedNonce = base64_encode($nonce);
$passSHA = base64_encode(sha1($nonce . $timestamp . sha1($password, true), true));
?>
it generates the below result with a 28 characters password digest, which I am using in soap requests, and it works fine
password_digest = '/pBYmwwc2cM87CUr8oB4Wkmyc0Q='
nonce = '���>�!��g��q�[�`�R��=J�o�'
nonce_base_64_encode = 'uqbkProhsR3JZxjC93HWW8BghQFSgqo9Sv9vGgUa4hs='
timestamp = '2022-01-13T18:28:52+00:00'
I need to do this same in python, but python is somehow generating longer password_digest and the soap request fails. I don't know if I am not generating the random_bytes correctly in python or some other issue. Below is python code:
import secrets
import hashlib
import datetime
clearPassword = 'MYSECRETPASSWORD'
created_at_timestamp_utc = datetime.datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%SZ")
def getSha1String(string):
stringInBytes = string.encode('ascii')
hash_object = hashlib.sha1(stringInBytes)
hex_dig = hash_object.hexdigest()
return hex_dig
def getBase64NonceString(nonce):
nonce_bytes = nonce.encode('ascii')
base64_bytes = base64.b64encode(nonce_bytes)
base64_nonce = base64_bytes.decode('ascii')
return base64_nonce
def getBase64String(string):
string_bytes = string.encode('ascii')
base64_bytes = base64.b64encode(string_bytes)
base64_string = base64_bytes.decode('ascii')
return base64_string
nonce = secrets.token_bytes(32)
base64_nonce = getBase64Nonce(nonce)
sha1_password = getSha1String(clearPassword)
password_digest = getBase64String(getSha1String(str(nonce) + created_at_timestamp_utc + sha1_password))
Your python code has 3 problems:
You're using binary output from sha1() in PHP, but hex output in python. Use digest(), not hexdigest(). This is why your output is longer in python.
Your timestamp format is incorrect.
The PHP format DATE_ATOM is "Y-m-d\TH:i:sP", where P outputs the UTC offset in the format +00:00. Unfortunately Python's strftime() doesn't seem to have an equivalent, but it's all in UTC anyway and your python code simply specifies the static string Z. So change that to +00:00, otherwise your tokens won't match.
Eg: timestamp = datetime.datetime.utcnow().strftime('%Y-%m-%dT%H:%M:%S+00:00')
You're using SECRETPASSWORD in PHP and MYSECRETPASSWORD in python, and I am emabarrased at how long I bashed my head against that one without noticing.
Succint working code:
import hashlib, datetime, base64
password = 'SECRETPASSWORD'
timestamp = '2022-01-13T18:28:52+00:00'
nonce = base64.b64decode('uqbkProhsR3JZxjC93HWW8BghQFSgqo9Sv9vGgUa4hs=')
def quickSHA(input):
return hashlib.sha1(input).digest()
def makeToken(password, timestamp, nonce):
return base64.b64encode(
quickSHA( nonce + timestamp + quickSHA(password) )
)
print makeToken(password, timestamp, nonce)
Output: /pBYmwwc2cM87CUr8oB4Wkmyc0Q=
Recently, I have a task to make HMAC to communicate API server.
I got a sample code of node.js version which makes HMAC of message. Using concept and sample, I've got to make a python code which is equivalent with node.js version but result is different, but I have no idea why.
Please review both code and help finding the difference.
Python 3.0
import hmac
import string
import hashlib
import base64
secret = 'PYPd1Hv4J6'
message = '1515928475.417'
key = base64.b64encode(secret.encode('utf-8'))
hmac_result = hmac.new(key, message.encode('utf-8'), hashlib.sha512)
print(base64.b64encode(hmac_result.digest()))
Result (Python 3.6)
b'7ohDRJGMGYjfHojnrvNpM3YM9jb+GLJjbQvblzrE17h2yoKfIRGEBSjfOqQFO4iKD7owk+gSciFxFkNB+yPP4g=='
Node.JS
var crypto = require('crypto');
var secret = 'PYPd1Hv4J6';
var message = '1515928475.417'
var key = Buffer(secret, 'base64');
var hmac = crypto.createHmac('sha512', key);
var hmac_result = hmac.update(message).digest('base64');
console.log(hmac_result)
Result (Node.JS 6.11)
m6Z/FxI492VXKDc16tO5XDNvty0Tmv0b1uksSbiwh87+4rmg43hEXM0WmWzkTP3aXB1s5rhm05Hu3g70GTrdEQ==
Your input keys are different, so the outputs will be different.
Node:
var secret = 'PYPd1Hv4J6';
var message = '1515928475.417'
var key = Buffer(secret, 'base64'); // buffer of bytes from the base64-encoded string 'PYPd1Hv4J6'
// <Buffer 3d 83 dd d4 7b f8 27>
Python:
secret = 'PYPd1Hv4J6'
message = '1515928475.417'
key = base64.b64encode(secret.encode('utf-8')) # did you mean b64decode here?
I was able to get them to match by stripping out the base64ing of everything:
Python:
import hmac
import string
import hashlib
import base64
secret = 'PYPd1Hv4J6'
message = '1515928475.417'
key = secret.encode('utf-8')
hmac_result = hmac.new(key, message.encode('utf-8'), hashlib.sha512)
print(base64.b64encode(hmac_result.digest()))
Output:
b'jezLNuBz37FoACm4LdLSqOQ5C93cuGID9a8MQmOZntXklDV3SvWdNfqndzK0a54awKeHY+behFiv4FYyILRoGQ=='
Javascript:
var crypto = require('crypto');
var secret = 'PYPd1Hv4J6';
var message = '1515928475.417'
var hmac = crypto.createHmac('sha512', secret);
var hmac_result = hmac.update(message).digest('base64');
console.log(hmac_result)
Output:
jezLNuBz37FoACm4LdLSqOQ5C93cuGID9a8MQmOZntXklDV3SvWdNfqndzK0a54awKeHY+behFiv4FYyILRoGQ==
Equivalent/Expected python code's is below.
import hmac
import string
import hashlib
import base64
secret = 'PYPd1Hv4J6=='
message = '1515928475.417'
key = base64.b64decode (secret.encode('utf-8'))
hmac_result = hmac.new(key, message.encode('utf-8'), hashlib.sha512)
print(base64.b64encode(hmac_result.digest()))
Padding '=' to targeted and decoding part was important.
Thank you.