SagePay AES form integration - encryption

I have a working Form Integration but need to move to v 3.00 of Form Integration which requires AES coding rather than Xor. The sample code is a J2EE app which hides away the method of encrypting. SagePay support have been unable to provide an API class/method for encrypting/decrypting; I suspect they are there, if anyone has used them and can advise on this?
Alternatively I am trying to do the encryption in my own code. Again SagePay have not been forthcoming, in that they don't provide enough information about how the encryption is done. I believe I have worked out that the password they provide is used both for the encryption key and iv, though they don't state this in the integration guide. Here is code I have tried for encoding inString
byte[] byteDataToEncrypt = inString.getBytes();
final byte[] keyBytes = webSite.encryptionPassword().getBytes("ASCII");
final byte[] ivBytes = webSite.encryptionPassword().getBytes("ASCII");
final SecretKey key = new SecretKeySpec(keyBytes, "AES");
final IvParameterSpec iv = new IvParameterSpec(ivBytes);
final Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, key, iv);
byte[] byteCipherText = cipher.doFinal(byteDataToEncrypt);
byte[] aesEncryptedBase64encoded = Base64.encodeBase64(byteCipherText);
String crypt = "#" + new String(aesEncryptedBase64encoded);
I do not know why # would need to be on the front, but I have seen it in other code that has been posted online for SagePay integration. Without it SagePay test server tells me the encryption method is not recognised. With it, it tells me the Currency field is missing, though I know the currency field is set ok to "GBP" in the source and I am using the provided test server password.
Any help very much appreciated whether from SagePay or from the developer community.
John

This works even if moving to ver4.00. The only thing that needs to be different for ver 4.00 is that it needs the encrypted string to be in upper case. Hence instead of
String crypt = "#" + Hex.encodeHexString(byteCipherText);
it will need to be
String crypt = "#" + Hex.encodeHexString(byteCipherText).toUpperCase();
Latest Sagepay/Opayo documentation at least verbally lists what all is needed and even if it is still not very comprehensive, together with this code, I could work it out.
Thanks.

The solution that worked for me is to replace this:
byte[] aesEncryptedBase64encoded = Base64.encodeBase64(byteCipherText);
String crypt = "#" + new String(aesEncryptedBase64encoded);
with this
String crypt = "#" + Hex.encodeHexString(byteCipherText);
or if using the SagePay API directly, replace all the code with just these two lines:
byte[] aesEncrypted = CryptographyHelper.AESEncrypt(detail, "ISO-8859-1", password);
String crypt = "#" + Hex.encodeHexString(aesEncrypted);

Related

AspNet Membership Encrypted Password Generation

Before we begin I want to make it clear that I know how bad this question is. It's a terrible situation but I'm being constrained by some very strange specifications.
I know that not only should you not try to write your own wrappers for this kind of stuff, but Microsoft have made it clear this shouldn't even be used. So before you start writing replies saying "Why are you even doing this" please try to understand that I have had these conversations with my superiors already, but the push for new features and lack of time means that despite it being atrocious; nevertheless - here I am.
We have an ASP Net membership database, started some time well before my time at this company and now hosting some 40k users. We have a platform in .Net 3.5 which lets users log in.
My job currently is to write an API in .Net Core 2.1 part of which is to allow for user creation and updating but there in lies the problem - migrating from Membership to Identity is not an option so I've been told to create a wrapper for the stored procedures in the Membership database.
This has been mostly successful with the only issue being; the subject of this question. Creating a user via aspnet_Membership_CreateUser I need to submit the data in such a way that it can be successfully validated in our platform.
I had originally followed this post but found that it's been designed for PasswordFormat 1 - Hashed; I then found that our user base used PasswordFormat 2 - Encrypted and as such the users I was creating would not validate.
The code looked something like this
public bool CreateUser(string userName, string password, string email, string securityQuestion, string securityAnswer, bool isApproved)
{
bool success = false;
//Just so I can attempt to login afterwards
password = "Hello World!";
//Our password and password salt need to be base64 encoded before we can save them to the DB
string salt = Guid.NewGuid().ToString();
string encryptedSalt = salt.Base64Encode();
//Concatenate our salt and password
IEnumerable<byte> saltedpass = salt.GetBytes(Encoding.UTF8).Concat(password.GetBytes(Encoding.UTF8));
//Use SHA1 to hash more - equivilant to the HASHBYTES('SHA1' T-SQL
byte[] sha1HashedPass = PasswordHelper.HashBytes(_validationMethod, saltedpass.ToArray(), _validationKey);
string hashedPass = sha1HashedPass.ToBase64String();
int errorCode = MembershipCreateUser(_applicationName, userName, hashedPass, encryptedSalt, email, securityQuestion, securityAnswer, isApproved);
if (errorCode == 0)
{
success = true;
}
return success;
}
Worth noting that _validationKey is the machine key shared across the applications which use this database, which I'm passing into the SHA1 mechanism.
So, intentionally and woefully bad security practice aside;
Is there a way in C# to generate an encrypted (not hashed) passwords and salts in this way?
Thank you for the comments - thankfully we were able to support Hashed passwords in our platform; the issue was with my code and not ASP Membership.
As mentioned I was taking a post that was originally written in T-SQL and trying to build a C# implementation of it. My implementation of this code was incorrect and as such the passwords and salts I was generating were not able to be validated by ASP Net Membership, this was not obvious in my original post because I had obfuscated the method which was SHA1 hashing my data.
//Using hard coded just for example
string username = "joeborder";
string password = "Hello World!";
string salt = "TastySalt";
Encoding encoder = Encoding.Unicode; //Encoding was also incorrect
//Our password and password salt need to be base64 encoded before we can save them to the DB
string encryptedSalt = salt.Base64Encode();
//Concatenate our salt and password
IEnumerable<byte> saltedpass = salt.GetBytes(encoder).Concat(password.GetBytes(encoder));
//Use SHA1 to hash more - equivilant to the HASHBYTES('SHA1') T-SQL
var SHA1Hasher = new SHA1CryptoServiceProvider(); //Originally I was using HMACSHA1 which was producing a different output
byte[] sha1HashedPass = SHA1Hasher.ComputerHash(saltedpass.ToArray());
string hashedPass = sha1HashedPass.ToBase64String();
/*
EXEC aspnet_Membership_CreateUser
#ApplicationName = "MyApp",
#UserName = username,
#Password = hashedPass,
#PasswordSalt = encryptedSalt,
...Etc
*/
Then in our .Net 3.5 application the following code would work
string username = "joeborder";
string password = "Hello World!";
if (Membership.ValidateUser(username, password))
{
Console.WriteLine("You've gotta be kidding me thats a clutch");
}

Salted Password Hashing. Am I doing it right in ASP.NET environment?

I am developing a website by using ASP.NET. I want to implement login authentication for my users. I am using SALT HASH method to securely save users' passwords to the DB. By looking at various codes I wrote a code like below to generate the SALT and the Hashed passwords to store in Database.
private string hashedPassword;
private string Salt;
private string SaltPlusPassword;
private const byte saltSize = 24;
byte[] saltArray;
byte[] hashedPasswordArray;
byte[] bytes;
public void Save_Login(string passWord)
{
using (RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider())
{
saltArray = new byte[saltSize];
rng.GetBytes(saltArray);
}
Salt = Convert.ToBase64String(saltArray);
SaltPlusPassword = String.Format("{0}{1}", Salt, passWord);
using (SHA256 sha = SHA256.Create())
{
bytes = Encoding.UTF8.GetBytes(SaltPlusPassword);
hashedPasswordArray = sha.ComputeHash(bytes);
}
hashedPassword = Convert.ToBase64String(hashedPasswordArray);
}
//Salt will be save to DB
//hashedPassword will be save to DB.
So I have few questions.
1) I read in an article that saying "make your salt is at least as long as the hash function's output" ok. What are the sizes for saltArray, hashedPasswordArray and bytes arrays which are declared in my code? I used saltArray size as 24. Is it ok?
2) What will happen if I use ?
bytes = Encoding.Unicode.GetBytes(SaltPlusPassword);
instead of
bytes = Encoding.UTF8.GetBytes(SaltPlusPassword);
3) What is the datatype should I use to store salt and the hashed password in the DB? ( My db is MYSQL )
4) Is there any performance difference if I use SHA256Managed instead of SHA256? Which is best?
5) Finally am I doing this in the right way? What are the weaknesses in above code? What are your suggestions?
Rather than deal with all these issues, why not use the built in identity management tools provided by ASP.NET. See here
http://www.asp.net/identity/overview/getting-started/introduction-to-aspnet-identity
Much more common and robust.

.Net Support of SHA256withRSA

I need to support the following signature:
Sign the UTF-8 representation of the input using SHA256withRSA (also known as RSASSA-PKCS1-V1_5-SIGN with the SHA-256 hash function) with the private key obtained from the Google Developers Console. The output will be a byte array.
The following code fails, with "Invalid algorithem specified". Is this a limitation of .NET? Here is a snippet of my code:
var rsa2 = new RSAPKCS1SignatureFormatter(rsa);
rsa2.SetHashAlgorithm("SHA256");
bytes = rsa2.CreateSignature(bytes);
The above requirement is from computing the signature for a Server to Server Applications for Google API.
https://developers.google.com/accounts/docs/OAuth2ServiceAccount#computingsignature
Thanks for any help.
Karl..
Here is the code that signs the JWT. I've removed the use of RSAPKC1SingatureFormatter class and using another HASHCompute method in an effort to get something to work (still not working)
I'm not sure this is correct and unfortunately my response from the Rest service is always the same "Invalid Grant" so hard to tell.
public string Generate(string HeadJWT, string ContentJWT, X509Certificate2 certificate)
{
var bytes = Utility.getBytes(HeadJWT);
var base64Head = Utility.Base64UrlEncode(bytes);
// base64 Url Encode Payload (Json Content)
bytes = Utility.getBytes(ContentJWT);
var base64Payload = Utility.Base64UrlEncode(bytes);
var secureInputValue = String.Format("{0}.{1}", base64Head, base64Payload);
bytes = Stub.Jwt.Utility.getBytes(secureInputValue);
bytes = Stub.Jwt.Utility.ComputeHMACSha265(bytes, certificate.PublicKey.EncodedKeyValue.RawData);
_signature = Stub.Jwt.Utility.Base64UrlEncode(bytes);
return String.Format("{0}.{1}.{2}", base64Head, base64Payload, _signature);
}
This cannot be a limitation of .NET in general, as the example Microsoft code seems to use "SHA256" itself. But it could be a limitation of your particular runtime.

Migrating users to Universal Membership Provider

I'm trying to migrate to the new Universal Membership providers (from a home brew solution). I've migrated our old User table to the Users/Memberships table.
When I run Membership.ValidateUser(txtUsername.Text.Trim(), txtPassword.Text.Trim()), it always returns false, even though I know the username/password is correct.
Here is how I generated the password, hash, and salt:
var salt = Crypto.GenerateSalt();
var hashedPassword = this.GenerateHashWithSalt(password, salt);
This is the GenerateHashWithSalt method I'm using
private string GenerateHashWithSalt(string password, string salt)
{
string hashWithSalt = password + salt;
byte[] saltedHashBytes = Encoding.UTF8.GetBytes(hashWithSalt);
HashAlgorithm algo = HashAlgorithm.Create(Membership.HashAlgorithmType);
byte[] hash = algo.ComputeHash(saltedHashBytes);
return Convert.ToBase64String(hash);
}
I've also gone with:
var salt = Crypto.GenerateSalt();
var saltedPassword = password + salt;
var hashedPassword = Crypto.HashPassword(saltedPassword);
Neither of these seem to work. What am I missing?
Scott
Bah, answered it.
So step 1 of all of this was migrating users from our 2-way encrypted passwords to 1 way hash.
Every example I saw of doing that included hashing the password manually. Turns out Membership.CreateUser(username,password); hashes the password (and salts), so I was hashing a hashed password, which is why auth was failing.
I simply had to call Membership.CreateUser, passing in the username & plain text pass, and it worked. Weee!

Unable to Generate HMac using Salesforce Crypto Class for Google Maps API

My Company recently signed up for Google Maps API for business.
To use the API, I need to generte a HMacSHA1 signature, and add it to my HTTP request. Unfortunately, somehow, I am not able to generate the right signature.
For testing, I am using the values provided by google to ensure that the algorithm works fine and I get the right result. Here is the code:
string url = 'maps/api/geocode/json?address=New+York&sensor=false&client=clientID';
string privateKey = 'vNIXE0xscrmjlyV-12Nj_BvUPaw=';
privateKey = privateKey.replace('-', '+');
privateKey = privateKey.replace('_', '/');
//Blob privateKeyBlob = EncodingUtil.base64Decode(privateKey);
Blob privateKeyBlob = Blob.valueOf(privateKey);
Blob urlBlob = Blob.valueOf(url);
Blob signatureBlob = Crypto.generateMac('HMacSHA1', urlBlob, privateKeyBlob);
String signature =EncodingUtil.urlEncode(EncodingUtil.base64Encode(signatureBlob), 'UTF-8');
signature = signature.replace('+', '-');
signature = signature.replace('/', '_');
system.debug('signature is ' +signature);
The generated signature should be : KrU1TzVQM7Ur0i8i7K3huiw3MsA=
Here is the link to Google Documentation where you can also find the same example: https://developers.google.com/maps/documentation/business/webservices
Few points to note:
1. I used the sample Python script provided in API Documentation and it gives the right result.
2. I think the problem is, API Documentation says that we should decode the privateKey and then provide it to the function. Although the documentation for Crypto Class says the "The value of privateKey does not need to be in decoded form.". I tried both, with and without decoding, still no result.
3. For Google API, everything has to be UTF-8 Encoded; I don't know if thats the way Encoding.Util decode's it.
I have tried a lot fo combinations, but could not find a solution. Any help would be highly appreciated.
Thanks,
Ankit
You need to sign the full path and query. Your string url is missing the leading slash (/).
Also, don't be afraid to open a support case with Google for this type of query.
We have used the code, but there are some defects in it, for whomever intend to use this code, the actual code will be:
string url = '/maps/api/geocode/json?address=New+York&sensor=false&client=clientID';
string privateKey = 'vNIXE0xscrmjlyV-12Nj_BvUPaw=';
privateKey = privateKey.replace('-', '+');
privateKey = privateKey.replace('_', '/');
Blob privateKeyBlob = EncodingUtil.base64Decode(privateKey);
Blob urlBlob = Blob.valueOf(url);
Blob signatureBlob = Crypto.generateMac('hmacSHA1', urlBlob, privateKeyBlob);
String signature = EncodingUtil.base64Encode(signatureBlob);
signature = signature.replace('+', '-');
signature = signature.replace('/', '_');
system.debug('*** Signature: ' + signature);

Resources