So I got an ASP.NET web app which uses hashing with salt to store password in mysql database. I am now trying to allow user to login with the same credentials as my web app through a php website. I used the following code to compare user input and hash in php
$pw = $salt . $extpassword;
if (!mb_check_encoding($pw, 'UTF-8')) {
$pw = mb_convert_encoding($pw, 'UTF-8');
}
return ($fromdb == base64_encode(hash('sha256',$pw, true)));
As for my code in c# used to generate hash:
System.Security.Cryptography.SHA256Managed sha256 = new System.Security.Cryptography.SHA256Managed();
byte[] hash = sha256.ComputeHash(System.Text.Encoding.UTF8.GetBytes(pw + salt));
return Convert.ToBase64String(hash);
I am not sure why this wouldn't work as I'm completely new to php. Can anyone please help?
In php you have salt + password, in .net you have pw + salt.
That will give different results. Fix by using:
$pw = $extpassword . $salt;
or
byte[] hash = sha256.ComputeHash(System.Text.Encoding.UTF8.GetBytes(salt + pw));
Related
i am trying to get session from my Chrome browser. i can see 2 cookie files in Developer Tools. but this is inconvenient for the user to get cookie values from browser, i would like to do it in code. so i use this code to get Chrome default profile cookie sqlite DB:
string local = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
string path = #"Google\Chrome\User Data\Default\Cookies";
path = Path.Combine(local, path);
next i create SQLite connection and request
var cmd = new SQLiteCommand("SELECT encrypted_value, name FROM cookies WHERE host_key = 'my_host_ip'", con);
then i read the results
byte[] encryptedCookie = (byte[])r.GetValue(r.GetOrdinal("encrypted_value"));
and try to decrypt it:
var decodedData = ProtectedData.Unprotect(encryptedCookie, null, DataProtectionScope.CurrentUser);
var plainText = Encoding.ASCII.GetString(decodedData);
and here i got exception
System.Security.Cryptography.CryptographicException
i know that i MUST decrypt cookie contents under the same user account under which the browser was launched (on the same machine), and parameter DataProtectionScope.CurrentUser is used for that
i see 63 bytes in debugger (in encryptedCookie array), i also see this bytes in SQLite DB BLOB field.
but Unprotect method throws System.Security.Cryptography.CryptographicException: Invalid data error.
my code works fine at 5 different PC's in my office (win10, win7), but didnt work on my developer PC (win10, vs2019).
i think that the problem is in my Windows Settings or somewhere else, not in my code. so what i am doing wrong?
interesting note - i found PowerShell script that does the same thing (through Add-Type -AssemblyName System.Security) - get cookie and decrypt it. this script also works fine at 5 office PC's, but didnt work at my PC.
my Windows installation is new, i have no AV software. we connected to the same Corporate domain and we have the same security settings.
UPD 1
a little expreriment:
get cookie value from Chrome browser (32 chars, JSESSIONID)
create a simple app that protects this value with CurrentUser protection scope. now i have an array of 178 bytes (result #1)
view Chrome's cookies database with a) https://sqliteonline.com/ and b) DataBase.Net desktop app. this two methods give me the same result: only 63 bytes of encrypted cookie data (result #2). i can also get the same result with my c# application using System.Data.SQLite
so, the results are not equal in length or content
result #1 != result #2
looks like Chrome's cookie value protected by different scope (maybe admin account?), but i see my user account name in Task Manager in Chrome's process
P.S. i use .net 4.7.2
UPD 2
i found this method in Chromium sources
bool OSCrypt::DecryptString(const std::string& ciphertext,
std::string* plaintext) {
if (!base::StartsWith(ciphertext, kEncryptionVersionPrefix,
base::CompareCase::SENSITIVE))
return DecryptStringWithDPAPI(ciphertext, plaintext);
crypto::Aead aead(crypto::Aead::AES_256_GCM);
auto key = GetEncryptionKeyInternal();
aead.Init(&key);
// Obtain the nonce.
std::string nonce =
ciphertext.substr(sizeof(kEncryptionVersionPrefix) - 1, kNonceLength);
// Strip off the versioning prefix before decrypting.
std::string raw_ciphertext =
ciphertext.substr(kNonceLength + (sizeof(kEncryptionVersionPrefix) - 1));
return aead.Open(raw_ciphertext, nonce, std::string(), plaintext);
}
so DPAPI is only used when BLOB NOT starts with v10 chars. but my cookie BLOBs starts with v10 chars, and, according to the code, another crypto-algorithm is used, but i dont understand WHY.
I finally figured it out. according to Chromium sources, two methods are used to decrypt the cookie value.
if the cookie value starts with v10 chars, we use AES_256_GCM
otherwise, DPAPI is used
for the first method we need key and nonce. key is located in Google Chrome files and nonce is located in encrypted cookie value.
it remains unclear for me - what determines which method is used
For people who are looking for the code, I'm expanding on Cerberus answer. Starting Chrome 80 version, cookies are encrypted using the AES256-GCM algorithm, and the AES encryption key is encrypted with the DPAPI encryption system, and the encrypted key is stored inside the ‘Local State’ file.
byte[] encryptedData=<data stored in cookie file>
string encKey = File.ReadAllText(localAppDataPath + #"\Google\Chrome\User Data\Local State");
encKey = JObject.Parse(encKey)["os_crypt"]["encrypted_key"].ToString();
var decodedKey = System.Security.Cryptography.ProtectedData.Unprotect(Convert.FromBase64String(encKey).Skip(5).ToArray(), null, System.Security.Cryptography.DataProtectionScope.LocalMachine);
_cookie = _decryptWithKey(encryptedData, decodedKey, 3);
Key size is 256 bits. Encypted message format is, pay load('v12')+nonce (12 bytes)+cipherText
private string _decryptWithKey(byte[] message, byte[] key, int nonSecretPayloadLength)
{
const int KEY_BIT_SIZE = 256;
const int MAC_BIT_SIZE = 128;
const int NONCE_BIT_SIZE = 96;
if (key == null || key.Length != KEY_BIT_SIZE / 8)
throw new ArgumentException(String.Format("Key needs to be {0} bit!", KEY_BIT_SIZE), "key");
if (message == null || message.Length == 0)
throw new ArgumentException("Message required!", "message");
using (var cipherStream = new MemoryStream(message))
using (var cipherReader = new BinaryReader(cipherStream))
{
var nonSecretPayload = cipherReader.ReadBytes(nonSecretPayloadLength);
var nonce = cipherReader.ReadBytes(NONCE_BIT_SIZE / 8);
var cipher = new GcmBlockCipher(new AesEngine());
var parameters = new AeadParameters(new KeyParameter(key), MAC_BIT_SIZE, nonce);
cipher.Init(false, parameters);
var cipherText = cipherReader.ReadBytes(message.Length);
var plainText = new byte[cipher.GetOutputSize(cipherText.Length)];
try
{
var len = cipher.ProcessBytes(cipherText, 0, cipherText.Length, plainText, 0);
cipher.DoFinal(plainText, len);
}
catch (InvalidCipherTextException)
{
return null;
}
return Encoding.Default.GetString(plainText);
}
}
Needed packages
1) Newtonsoft JSON .net
2) Bouncy Castle Crypto package
I am in need of assistance with signing a string in PHP. We are building a web application which is able to alter profile data on an external website. We want to achieve this without requiring end users to enter our password on our site. The external website therefore created an API which allows us to edit the profile as long we send them email address of the profile signed by a certificate. This allows them to check whether the request came from a trusted source (us).
We tried both native PHP functions and PHPSeclib to sign an email address using a certificate:
$rsa = new RSA();
$rsa->setHash("sha1");
$rsa->setSignatureMode(RSA::SIGNATURE_PKCS1);
$originalkey = file_get_contents('key.ppk');
$rsa->loadKey($originalkey);
echo bin2hex($rsa->sign("test#email.nl"));
// Using native PHP methods
$email = 'test#email.nl';
$signature = '';
$private_key = openssl_pkey_get_private(file_get_contents("key.ppk"));
var_dump(openssl_sign($email, $signature, $private_key, "sha1"));
echo bin2hex($signature);
The signatures are fine because PHP is able to verify the posted signatures. The webservice of the external website does not accept our signatures however. They only allow a signature length of 40 characters whilst a hexidecimal dump of our signature exceeds 1000 characters.
They sent us this C# example to sign a string but we do not have enough C# knowledge to see why the output differs.
string Sign(string username, string thumbprint, string hasher = "SHA1")
{
var store = new
System.Security.Cryptography.X509Certificates.X509Store(System.Security.Cryptography.X509Certificates.StoreLocation.LocalMachine);
store.Open(System.Security.Cryptography.X509Certificates.OpenFlags.OpenExistingOnly |
System.Security.Cryptography.X509Certificates.OpenFlags.ReadOnly);
try
{
foreach (System.Security.Cryptography.X509Certificates.X509Certificate2 certificate in store.Certificates.Find(System.Security.Cryptography.X509Certificates.X509FindType.FindByThumbprint, thumbprint, false))
{
var privateKey = certificate.PrivateKey as System.Security.Cryptography.RSACryptoServiceProvider;
if (privateKey != null)
{
var bytes = privateKey.SignData(Encoding.UTF8.GetBytes(username),
System.Security.Cryptography.HashAlgorithm.Create(hasher));
return string.Join("", bytes.Select(b => b.ToString("x2")));
} else throw new ArgumentException("no private key");
}
}
finally
{
store.Close();
}
return null;
}
Is there anyone who can point us to the right direction?
Thanks in advance!
So I was coding a simple login registration windows app on visual studio as I wanted to start learning again C#
I have this registration page with a few fields, the code is very simple, I didn't do anything related to validation.
I am basically trying to hash the password using SHA1 in c# and output in another textbox, but I am a getting some unknown characters
Here is my code
private void button1_Click(object sender, EventArgs e)
{
if (username.Text.Trim()=="" || password.Text=="" || passwordc.Text=="" || fname.Text == "" || lname.Text == "" || birthday.Text == "")
{
MessageBox.Show("Please fill all the fields!");
}
else if (password.Text!=passwordc.Text) {
MessageBox.Show("Passwords don't match !");
}
else
{
String passwd="";
passwd = password.Text;
byte[] bytes = System.Text.Encoding.UTF8.GetBytes(passwd);
SHA1 sha = new SHA1CryptoServiceProvider();
byte[] passbyte = sha.ComputeHash(bytes);
string pass = System.Text.Encoding.UTF8.GetString(passbyte);
textBox1.Text = pass;
}
https://i.stack.imgur.com/SOPit.png
Is there anything wrong or is this a normal hashing? I am kinda confused now.
Thanks
The hash is just a bunch of bytes. They're not meaningful characters. If you want to turn the hash into a textual form, you can use Convert.ToBase64String():
string pass = Convert.ToBase64String(passByte);
Also, consider switching to SHA256. SHA1 is becoming easier and easier to crack as computing power increases. One common thing to do is to run the hashing process (the new hash becomes the password to hash) hundreds or thousands of times before storing it. It doesn't make too significant a difference in time for creating a hash to store, but it makes cracking the password take that many times longer.
Also, you'll want to start incorporating a random salt to each password to hash. This avoids rainbow table attacks and won't generate the same hash for identical passwords.
I try to decrypt data which is encrypted with PHP from local money transfer service.
There is PHP example as following:
<?php
require_once('phpseclib/Crypt/AES.php');
define('API_PASSKEY', 'abcdefghijklmnop');
if($_SERVER['REMOTE_ADDR'] == '203.146.127.115' && isset($_GET['request']))
{
$aes = new Crypt_AES();
$aes->setKey(API_PASSKEY);
$_GET['request'] = base64_decode(strtr($_GET['request'], '-_,', '+/='));
$_GET['request'] = $aes->decrypt($_GET['request']);
if($_GET['request'] != false)
{
parse_str($_GET['request'],$request);
$request['Ref1'] = base64_decode($request['Ref1']);
echo 'SUCCEED';
}
else
{
echo 'ERROR|INVALID_PASSKEY';
}
}
else
{
echo 'ERROR|ACCESS_DENIED';
}
?>
However, I want to do decrypt with C#.
I also set up simple PHP encrypt text for testing but cannot decrypt with C# either.
<?php
include('phpseclib/Crypt/AES.php');
$aes = new Crypt_AES();
$aes->setKey('abcdefghijklmnop');
$plaintext = 'Hello';
$cryptoText = $aes->encrypt($plaintext) ;
$cryptoText = base64_encode( $cryptoText);
echo $cryptoText . "<br/>";
echo $aes->decrypt(base64_decode($cryptoText));
?>
I looking forward to get your good suggestion.
phpseclib uses CBC by default with PKCS#7 padding enabled and 128-bit keys if that helps.
what i am trying to do is when a user registers the password gets encrypted, and the encrypted password gets saved in a database, and when the user logs in it should then decrypt the password to compare if the user entered the correct password, but when i try to decrypt its gives me a "Bad data" exception.
Please help guys.
Here is my code:
protected void btnLogin_Click(object sender, EventArgs e)
{
try
{
private Cryptography crypt = new Cryptography();
var registerUser = new test.Model.User();
registerUser.EmailAddress = txtEmail.Text;
registerUser.Password = txtPassword.Text;
//new test().Getbyusername(registerUser);
new test().getbyemail(registerUser, crypt);
}
catch (Exception ex)
{
}
}
public void getbyemail(User user, Cryptography crypt)
{
try
{
var repo = new UserRepository();
var test = repo.GetEncryptedPasswrd(user);
var o = repo.getPrivateKey(user.EmailAddress);
crypt.privateKey = o;
var j = repo.getpublicKey(user.EmailAddress);
crypt.publicKey = j;
decryptPassword(test, o, crypt);
}
catch (Exception ex)
{
}
}
public String decryptPassword(byte [] encryptedpassword, string privateKey, Cryptography cry)
{
decrypted = cry.decrypt(encryptedpassword, privateKey);
//return Encoding.ASCII.GetString(decrypted);
return Encoding.ASCII.GetString(decrypted);
}
protected void btnRegister_Click(object sender, EventArgs e)
{
Cryptography crypt = new Cryptography();
var registerUser = new test.Model.User();
registerUser.Name = txtName.Text;
registerUser.Surname = txtSurname.Text;
registerUser.EmailAddress = txtEmailAddress.Text;
registerUser.Password = txtPassword.Text;
registerUser.DateRegisterd = DateTime.Now;
new test().RegisterUser(registerUser, crypt.privateKey, crypt.publicKey,crypt, encrypted);
}
public void RegisterUser(User user, string privateKey, string publicKey, Cryptography crypt, byte[] encrypted)
{
try
{
var repo = new UserRepository();
byte[] plainText = Encoding.ASCII.GetBytes(user.Password);
encrypted = crypt.encrypt(plainText, crypt.publicKey);
user.Password = Encoding.ASCII.GetString(encrypted);
user.PrivateKey = crypt.privateKey;
user.PublickKey = crypt.publicKey;
repo.Add(user);
}
catch (Exception ex)
{
}
}
Thanks in advance.
As said above comment you should really hash it .
Still if you want to encrypt as you example don't decrypt password . Instead you should encrypt password from user and simply compare to database .
You can Consider this simple option to hash the password . http://davidhayden.com/blog/dave/archive/2004/02/16/157.aspx .
You should not be encrypting passwords. Encryption is a reversable process, so if someone were to steal the encryption key and the passwords, they could get the user's password.
Instead, you should hash the password, and compare the hashes. A hash is destructive - it is impossible to get the original data from a hash. When a user signs up, you store the hash. When they want to sign back in, you hash what they entered and compare the hashes in the database. A hash using an algorithm like SHA-256 can be done like this:
public string GetPasswordHash(string clearPassword)
{
using (var hash = new System.Security.Cryptography.SHA256Managed())
{
var hashBytes = System.Text.Encoding.UTF8.GetBytes(clearPassword);
return Convert.ToBase64String(hash.ComputeHash(hashBytes));
}
}
This gets us a step further, but you should also use a salt as well to prevent attacks like Rainbow Tables. In addition, hashing it multiple times (say 10,000) helps prevent against against brute force attacks. 10,000 hashes is fast for the user logging in, but extremely slow trying to brute force.
I would start with writing a unit test that takes a password, encrypts it an immediately decrypts it.
Once you know that works, make a copy of the encrypted password, and test if you can make a successful roundtrip to the database. If that is binary, encoding it to hex or base64 might help.
there can be a security hole when you're able to decrypt the password. What you should do is encrypt the submitted password as well and compare the encrypted strings.
EDIT: thanks Matthew... that's what i meant...doh
the better question is why aren't you making full use of .net built in login control? You'll need to configure your web.config.
for best security. add the following in your membership provider settings in web.config
enablePasswordRetrieval="False" enablePasswordReset="True" passwordFormat="Hashed"
also add machinekey in
<system.web>
http://www.qualitydata.com/products/aspnet-membership/help/configuration/no-machinekey.aspx