Decrypt a PBKDF2 encrypted Session Key in PHP 7.2 - encryption

I need to upgrade a website I look after to PHP 7.2. In the testing phase i've discovered that one of the plug-in's on the site used the mcrypt library which is no longer available in PHP 7.2.
Essentially this plugin receives PBKDF2 encrypted data from a ticketing system (Tessitura) that returns the user's session key and a time stamp and an encrypted string.
In the control panel I have been given data to use to decrypt this session key such as a Passphrase, Salt, Authentication/HMAC Key , BlockSize, PaddingMode, EncryptionKeyIterations, EncryptionKeyLength and HMACLength
Image of supplied fields
I've been trying to work out how to decrypt the data but I confess i'm struggling. C
an anybody tell me how to use php 7.2 to achieve this? I've found some functions in the openssl suite that look like they may be the correct way to go but they all use different terminology to the information i've been given and I cannot work out where to start, what goes where or what settings to use
Thanks in advance to anybody that can solve this problem!!

As Rob Napier said, PBKDF2 is what the system is using to hash the password being passed into the encryption process. The site is actually using aes-256-cbc encryption. That encryption process can include a password.
After the information is encrypted, that payload is signed with an HMAC key.
You can use the openSSL library to execute all of this in php 7 and higher. Here is some sample code that creates a class to handle the encryption/decryption, for example:
$crypto = new AesCryptoClass('YOUR_PASSPHRASE_HERE',
'YOUR_HMAC_KEY_HERE',
'YOUR_SALT_HERE');
class AesCryptoClass {
// These should not change
private $hmacLength = 32;
private $iterations = 1000;
private $keyLength = 32;
private $blockSize = 16;
private $cipher = 'aes-256-cbc';
function __construct($password,$hmacKey,$salt)
{
$this->password = $password;
$this->hmacKey = $hmacKey;
$this->salt = $salt;
}
function encrypt($plainText)
{
$iv = openssl_random_pseudo_bytes(16);
$encryptedBytes = $this->encryptInner($iv, $plainText);
$encryptedMessage = $iv . $encryptedBytes;
$mac = $this->hashMessage($encryptedMessage);
$secureMessage = $mac . $encryptedMessage;
$encryptedText = base64_encode($secureMessage);
return $encryptedText;
}
function decrypt($encryptedText)
{
$secureMessage = base64_decode($encryptedText);
$mac = substr($secureMessage, 0, $this->hmacLength);
$encryptedMessage = substr($secureMessage, $this->hmacLength);
$newMac = $this->hashMessage($encryptedMessage);
if (strcmp($mac, $newMac) !== 0) {
return "";
}
$iv = substr($encryptedMessage,0, $this->blockSize);
$encryptedBytes = substr($encryptedMessage, $this->blockSize);
$plainText = $this->decryptInner($iv, $encryptedBytes);
return $plainText;
}
function encryptInner($iv, $plainText)
{
$encryptionKey = openssl_pbkdf2($this->password, $this->salt, $this->keyLength, $this->iterations);
return openssl_encrypt($plainText, $this->cipher, $encryptionKey, OPENSSL_RAW_DATA, $iv);
}
function decryptInner($iv, $encryptedBytes)
{
$encryptionKey = openssl_pbkdf2($this->password, $this->salt, $this->keyLength, $this->iterations);
return openssl_decrypt($encryptedBytes, $this->cipher, $encryptionKey, OPENSSL_RAW_DATA, $iv);
}
function hashMessage($encryptedMessage)
{
return pack("H*", hash_hmac("sha256", $encryptedMessage, $this->hmacKey));
}
}
This code and the description of the process are also included here at the bottom of the wiki:
https://bitbucket.org/TN_WebShare/webpro-session-sharing-sample/wiki/Session%20Key%20Encryption%20and%20Decryption

Related

C# BouncyCastle CmsEnvelope key encryption algorithm RSA-OAEP (SHA256, MGF1SHA256)

I'm using BouncyCastle (BC) API for C#.
After Hours going through this API hell i figured out how to sign/verify and encrypt/decrypt using CMS with BC.
I have to encrypt the symmetric key for the CMSEnvelope with RSA-OAEP (SHA256 + MGF1 with SHA256) as key encryption algorithm.
On default BC obviously uses RSA.
In RecipientInfo structure of the CMS envelope I have to use KeyTransRecipientInfo.
I can't figure out how to set the RSA-OAEP-algorithm to the recipientInfo:
//Create Encrypted cms envelope
X509Certificate otherscert = new X509CertificateParser().ReadCertificate(myCertBytes)
CmsEnvelopedDataGenerator envDataGen = new CmsEnvelopedDataGenerator();
envDataGen.AddKeyTransRecipient(othersCert); //setting Cert, but how the key encr. algorithm?
CmsProcessableByteArray sData = new CmsProcessableByteArray(signedData.GetEncoded());
var envData = envDataGen.Generate(sData, CmsEnvelopedDataGenerator.Aes256Cbc); //generate the envelope
Now in the recipientInfo KeyEncryptionAlgOid is 1.2.840.113549.1.1.1 (RSA)
I need it to be 1.2.840.113549.1.1.7 (RSA-OAEP) with the mentioned key encryption parameters.
Any idea how to do that in BC's C#-API?
EDIT:
Now I got the Source from Git and did the following edits.
The problem was not just setting the keyEncryptionAlgorithm to the recipient.
BC's logic takes the certificates SubjectPublicKeyInfo-algorithm for key encryption.
in CMSEnvelopedGenerator.cs I added
public void AddKeyTransRecipientRsaOaep(
X509Certificate cert, DerObjectIdentifier digest, DerObjectIdentifier mgf1digest, AlgorithmIdentifier pSource = null)
{
if (pSource == null)
{
pSource = new AlgorithmIdentifier(PkcsObjectIdentifiers.IdPSpecified, new DerOctetString(new byte[0]));
}
AlgorithmIdentifier hash = new AlgorithmIdentifier(digest, DerNull.Instance);
AlgorithmIdentifier mask = new AlgorithmIdentifier(PkcsObjectIdentifiers.IdMgf1, mgf1digest);
var rsaOaepParams = new RsaesOaepParameters(hash, mask, pSource);
KeyTransRecipientInfoGenerator ktrig = new KeyTransRecipientInfoGenerator();
ktrig.RecipientCert = cert;
ktrig.AlgorithmId = new AlgorithmIdentifier(PkcsObjectIdentifiers.IdRsaesOaep, rsaOaepParams);
recipientInfoGenerators.Add(ktrig);
}
In KeyTransrecipientInfoGenerator.cs I added
private AlgorithmIdentifier keyEncryptionAlg;
...
internal AlgorithmIdentifier AlgorithmId
{
set
{
keyEncryptionAlg = value;
}
}
...
protected virtual AlgorithmIdentifier AlgorithmDetails
{
get
{
if (keyEncryptionAlg == null)
{
return info.AlgorithmID;
}
else
{
return keyEncryptionAlg;
}
}
}
//In the Generate() method I changed
keyEncryptionAlgorithm = AlgorithmDetails;
//to
AlgorithmIdentifier keyEncryptionAlgorithm;
if (this.keyEncryptionAlg != null)
{
keyEncryptionAlgorithm = this.keyEncryptionAlg;
}
else
{
keyEncryptionAlgorithm = AlgorithmDetails;
}
Now the keyEncryption algorithm and parameters are set correctly and it encrypts without exception:
CmsEnvelopedDataGenerator envDataGen = new CmsEnvelopedDataGenerator();
envDataGen.AddKeyTransRecipientRsaOaep(othersCert, NistObjectIdentifiers.IdSha256, NistObjectIdentifiers.IdSha256);
CmsProcessableByteArray sData = new CmsProcessableByteArray(signedData.GetEncoded());
var envData = envDataGen.Generate(sData, CmsEnvelopedDataGenerator.Aes256Cbc);
Reading the Envelope with BC or NetCore3 works fine.
The RecipientInfos are set as intended (keyEncryptionAlgorithm, params).
But Decryption fails on both... :(

Encryption with DES with urlENcode(encrypt)

I am attempting to use DES to encrypt / decrypt. However, the decrypt is not working. I keep getting the error: Cannot read property 'charCodeAt' of null
The decryption is from http://www.tero.co.uk/des/
I am using this to encode:
var decodedKey = decodeBase64(key);
var em = urlEncode(encrypt(decodedKey, value));
and then I am attempting to use this function to decrypt:
var emD = urlDecode(decrypt(decodedKey, em))
with
function encrypt(key, message) {
var ciphertext = des(key, message, 1, 0, null, 1);
var encodedCiphertext = encodeBase64(ciphertext);
return encodedCiphertext;
}
function decrypt(key,message) {
var ciphertext = des(key, message, 0, 1, null,1);
var plainText = decodeBase64(ciphertext);
return plainText;
};
With:
urlEncode(encrypt(decodedKey, value));
you first encrypt then url-encode. When you need to decrypt, you need to first url-decode then decrypt - something like:
decrypt(decodedKey, urlDecode(em));
as opposed to decrypt then url-decode as you try with this statement:
urlDecode(decrypt(decodedKey, em));
Also, please note that DES is considered insecure and should be avoided. Use a secure cipher like AES.

GnuPG message encryption system

I want to build a message encryption system where users will send message in a encrypted format. I am using GnUPG.I got help from http://www.php.net/manual/en/gnupg.installation.php to install the GnUPG. After install in the server I create the public and private keyring by following code
$GeneratedKey = $gpg->GenKey($name, $comment, $email, $passphrase,$ExpireDate, $KeyType, $KeyLength,$SubkeyType, $SubkeyLength );
function GenKey($RealName, $Comment, $Email, $Passphrase = '', $ExpireDate = 0, $KeyType = 'DSA', $KeyLength = 1024, $SubkeyType = 'ELG-E', $SubkeyLength = 1024)
{
// validates the keytype
if (($KeyType != 'DSA') && ($KeyType != 'RSA')) {
$this->error = 'Invalid Key-Type, the allowed are DSA and RSA';
return false;
}
// validates the subkey
if ((!empty($SubkeyType)) && ($SubkeyType != 'ELG-E')) {
$this->error = 'Invalid Subkey-Type, the allowed is ELG-E';
return false;
}
// validate the expiration date
if (!preg_match('/^(([0-9]+[dwmy]?)|([0-9]{4}-[0-9]{2}-[0-9]{2}))$/', $ExpireDate)) {
$this->error = 'Invalid Expire Date, the allowed values are <iso-date>|(<number>[d|w|m|y])';
return false;
}
// generates the batch configuration script
$batch_script = "Key-Type: $KeyType\n" .
"Key-Length: $KeyLength\n";
if (($KeyType == 'DSA') && ($SubkeyType == 'ELG-E'))
$batch_script .= "Subkey-Type: $SubkeyType\n" .
"Subkey-Length: $SubkeyLength\n";
$batch_script .= "Name-Real: $RealName\n" .
"Name-Comment: $Comment\n" .
"Name-Email: $Email\n" .
"Expire-Date: $ExpireDate\n" .
"Passphrase: $Passphrase\n" .
"%commit\n" .
"%echo done with success\n";
// initialize the output
$contents = '';
// execute the GPG command
if ( $this->_fork_process($this->program_path . ' --homedir ' . $this->home_directory .
' --batch --status-fd 1 --gen-key',
$batch_script, $contents) ) {
$matches = false;
if ( preg_match('/\[GNUPG:\]\sKEY_CREATED\s(\w+)\s(\w+)/', $contents, $matches) )
return $matches[2];
else
return true;
} else
return false;
}
I encrypt by the following code
$gpg = new gnupg();
$gpg->addencryptkey($recipient);
$ciphertext = $gpg->encrypt($plaintext);
Decrypt by the following code
$gpg = new gnupg();
$gpg->adddecryptkey($recipient, $receiver_passphrase);
$plain = $gpg->decrypt($encrypted_text, $plaintext);
BY this I successfully create a folder of a username and generate private and public keyring over there and then send message in a encrypted way and decrypt by the receiver. But my main concern is I don't want to generate user public and private keyring in the server, instead I want to generate public and private keyring in users local computer..
Is it possible to generate public and private key in local computer? Because I don't want user depend on server security. Only receiver will able to decrypt the message.. Not other will able to decrypt..
thanks,
You can create the keys using OpenPGP.js which runs in your client's browser, store the private key somewhere with your client and send nothing but the public one to the server.
// Create new key with RSA encryption (1), 4k length for
// John Doe with password "foobar"
var keys = openpgp.generate_key_pair(1, 4096,
"John Doe john.doe#example.org", "foobar");
keys.privateKeyArmored; // Access private key
keys.publicKeyArmored; // Access public key

AES in ASP.NET with VB.NET

What is a good link or article on encrypting a URL link with AES to pass username to another web site in ASP.NET using VB.NET 2005?
FYI: The receiving web site will have access to the private KEY to decrypt.
First
Don't do it! Writing your own crypto system can easily lead to making mistakes. It's best to use an existing system, or if not, get someone who knows cryptography to do it. If you have to do it yourself, read Practical Cryptography.
And please, remember: "We already have enough fast, insecure systems." (Bruce Schneier) -- Do things correct and worry about performance later.
That said, if you are stuck on using AES to roll your own, here are a few pointers.
Initialization Vector
AES is a block cipher. Given a key and a block of plaintext, it converts it to a specific ciphertext. The problem with this is that the same blocks of data will generate the same ciphertext with the same key, every time. So suppose you send data like this:
user=Encrypt(Username)&roles=Encrypt(UserRoles)
They're two separate blocks, and the UserRoles encryption will have the same ciphertext each time, regardless of the name. All I need is the ciphertext for an admin, and I can drop it right in with my cipher'd username. Oops.
So, there are cipher operation modes. The main idea is that you'll take the ciphertext of one block, and XOR it into the ciphertext of the next block. That way we'll do Encrypt(UserRoles, Username), and the Username ciphertext is affected by the UserRoles.
The problem is that the first block is still vulnerable - just by seeing someone's ciphertext, I might know their roles. Enter the initialization vector. The IV "starts up" the cipher and ensures it has random data to encrypt the rest of the stream. So now the UserRoles ciphertext has the ciphertext of the random IV XOR'd in. Problem solved.
So, make sure you generate a random IV for each message. The IV is not sensitive and can be sent plaintext with the ciphertext. Use an IV large enough -- the size of the block should be fine for many cases.
Integrity
AES doesn't provide integrity features. Anyone can modify your ciphertext, and the decrypt will still work. It's unlikely it'll be valid data in general, but it might be hard to know what valid data is. For instance, if you're transmitting a GUID encrypted, it'd be easy to modify some bits and generate a completely different one. That could lead to application errors and so on.
The fix there is to run a hash algorithm (use SHA256 or SHA512) on the plaintext, and include that in the data you transmit. So if my message is (UserName, Roles), you'll send (UserName, Roles, Hash(UserName, Roles)). Now if someone tampers with the ciphertext by flipping a bit, the hash will no longer compute and you can reject the message.
Key derivation
If you need to generate a key from a password, use the built-in class: System.Security.Cryptography.PasswordDeriveBytes. This provides salting and iterations, which can improve the strength of derived keys and reduce the chance of discovering the password if the key is compromised.
Timing/replay
Edit: Sorry for not mentioning this earlier :P. You also need to make sure you have an anti-replay system. If you simply encrypt the message and pass it around, anyone who gets the message can just resend it. To avoid this, you should add a timestamp to the message. If the timestamp is different by a certain threshold, reject the message. You may also want to include a one-time ID with it (this could be the IV) and reject time-valid messages that come from other IPs using the same ID.
It's important to make sure you do the hash verification when you include the timing information. Otherwise, someone could tamper with a bit of the ciphertext and potentially generate a valid timestamp if you don't detect such brute force attempts.
Sample code
Since apparently using an IV correctly is controversial for some folks, here's some code that'll generate random IVs and add them to your output for you. It'll also perform the authentication step, making sure the encrypted data wasn't modified.
using System;
using System.Security.Cryptography;
using System.Text;
class AesDemo {
const int HASH_SIZE = 32; //SHA256
/// <summary>Performs encryption with random IV (prepended to output), and includes hash of plaintext for verification.</summary>
public static byte[] Encrypt(string password, byte[] passwordSalt, byte[] plainText) {
// Construct message with hash
var msg = new byte[HASH_SIZE + plainText.Length];
var hash = computeHash(plainText, 0, plainText.Length);
Buffer.BlockCopy(hash, 0, msg, 0, HASH_SIZE);
Buffer.BlockCopy(plainText, 0, msg, HASH_SIZE, plainText.Length);
// Encrypt
using (var aes = createAes(password, passwordSalt)) {
aes.GenerateIV();
using (var enc = aes.CreateEncryptor()) {
var encBytes = enc.TransformFinalBlock(msg, 0, msg.Length);
// Prepend IV to result
var res = new byte[aes.IV.Length + encBytes.Length];
Buffer.BlockCopy(aes.IV, 0, res, 0, aes.IV.Length);
Buffer.BlockCopy(encBytes, 0, res, aes.IV.Length, encBytes.Length);
return res;
}
}
}
public static byte[] Decrypt(string password, byte[] passwordSalt, byte[] cipherText) {
using (var aes = createAes(password, passwordSalt)) {
var iv = new byte[aes.IV.Length];
Buffer.BlockCopy(cipherText, 0, iv, 0, iv.Length);
aes.IV = iv; // Probably could copy right to the byte array, but that's not guaranteed
using (var dec = aes.CreateDecryptor()) {
var decBytes = dec.TransformFinalBlock(cipherText, iv.Length, cipherText.Length - iv.Length);
// Verify hash
var hash = computeHash(decBytes, HASH_SIZE, decBytes.Length - HASH_SIZE);
var existingHash = new byte[HASH_SIZE];
Buffer.BlockCopy(decBytes, 0, existingHash, 0, HASH_SIZE);
if (!compareBytes(existingHash, hash)){
throw new CryptographicException("Message hash incorrect.");
}
// Hash is valid, we're done
var res = new byte[decBytes.Length - HASH_SIZE];
Buffer.BlockCopy(decBytes, HASH_SIZE, res, 0, res.Length);
return res;
}
}
}
static bool compareBytes(byte[] a1, byte[] a2) {
if (a1.Length != a2.Length) return false;
for (int i = 0; i < a1.Length; i++) {
if (a1[i] != a2[i]) return false;
}
return true;
}
static Aes createAes(string password, byte[] salt) {
// Salt may not be needed if password is safe
if (password.Length < 8) throw new ArgumentException("Password must be at least 8 characters.", "password");
if (salt.Length < 8) throw new ArgumentException("Salt must be at least 8 bytes.", "salt");
var pdb = new PasswordDeriveBytes(password, salt, "SHA512", 129);
var key = pdb.GetBytes(16);
var aes = Aes.Create();
aes.Mode = CipherMode.CBC;
aes.Key = pdb.GetBytes(aes.KeySize / 8);
return aes;
}
static byte[] computeHash(byte[] data, int offset, int count) {
using (var sha = SHA256.Create()) {
return sha.ComputeHash(data, offset, count);
}
}
public static void Main() {
var password = "1234567890!";
var salt = new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };
var ct1 = Encrypt(password, salt, Encoding.UTF8.GetBytes("Alice; Bob; Eve;: PerformAct1"));
Console.WriteLine(Convert.ToBase64String(ct1));
var ct2 = Encrypt(password, salt, Encoding.UTF8.GetBytes("Alice; Bob; Eve;: PerformAct2"));
Console.WriteLine(Convert.ToBase64String(ct2));
var pt1 = Decrypt(password, salt, ct1);
Console.WriteLine(Encoding.UTF8.GetString(pt1));
var pt2 = Decrypt(password, salt, ct2);
Console.WriteLine(Encoding.UTF8.GetString(pt2));
// Now check tampering
try {
ct1[30]++;
Decrypt(password, salt, ct1);
Console.WriteLine("Error: tamper detection failed.");
} catch (Exception ex) {
Console.WriteLine("Success: tampering detected.");
Console.WriteLine(ex.ToString());
}
}
}
Output:
JZVaD327sDmCmdzY0PsysnRgHbbC3eHb7YXALb0qxFVlr7Lkj8WaOZWc1ayWCvfhTUz/y0QMz+uv0PwmuG8VBVEQThaNTD02JlhIs1DjJtg=
QQvDujNJ31qTu/foDFUiVMeWTU0jKL/UJJfFAvmFtz361o3KSUlk/zH+4701mlFEU4Ce6VuAAuaiP1EENBJ74Wc8mE/QTofkUMHoa65/5e4=
Alice; Bob; Eve;: PerformAct1 Alice;
Bob; Eve;: PerformAct2 Success:
tampering detected.
System.Security.Cryptography.CryptographicException:
Message hash incorrect. at
AesDemo.Decrypt(String password,
Byte[] passwordSalt, Byte[]
cipherText) in
C:\Program.cs:line
46 at AesDemo.Main() in
C:\Program.cs:line
100
After removing the random IV and the hash, here's the type of output:
tZfHJSFTXYX8V38AqEfYVXU5Dl/meUVAond70yIKGHY=
tZfHJSFTXYX8V38AqEfYVcf9a3U8vIEk1LuqGEyRZXM=
Notice how the first block, corresponding to "Alice; Bob; Eve;" is the same. "Corner case" indeed.
Example without hashing
Here's a simple example of passing a 64-bit integer. Just encrypt and you're open to attack. In fact, the attack is easily done, even with CBC padding.
public static void Main() {
var buff = new byte[8];
new Random().NextBytes(buff);
var v = BitConverter.ToUInt64(buff, 0);
Console.WriteLine("Value: " + v.ToString());
Console.WriteLine("Value (bytes): " + BitConverter.ToString(BitConverter.GetBytes(v)));
var aes = Aes.Create();
aes.GenerateIV();
aes.GenerateKey();
var encBytes = aes.CreateEncryptor().TransformFinalBlock(BitConverter.GetBytes(v), 0, 8);
Console.WriteLine("Encrypted: " + BitConverter.ToString(encBytes));
var dec = aes.CreateDecryptor();
Console.WriteLine("Decrypted: " + BitConverter.ToUInt64(dec.TransformFinalBlock(encBytes, 0, encBytes.Length), 0));
for (int i = 0; i < 8; i++) {
for (int x = 0; x < 250; x++) {
encBytes[i]++;
try {
Console.WriteLine("Attacked: " + BitConverter.ToUInt64(dec.TransformFinalBlock(encBytes, 0, encBytes.Length), 0));
return;
} catch { }
}
}
}
Output:
Value: 6598637501946607785 Value
(bytes): A9-38-19-D1-D8-11-93-5B
Encrypted:
31-59-B0-25-FD-C5-13-D7-81-D8-F5-8A-33-2A-57-DD
Decrypted: 6598637501946607785
Attacked: 14174658352338201502
So, if that's the kind of ID you're sending, it could quite easily be changed to another value. You need to authenticate outside of your message. Sometimes, the message structure is unlikely to fall into place and can sorta act as a safeguard, but why rely on something that could possibly change? You need to be able to rely on your crypto working correctly regardless of the application.
I wrote a blog post which has a sample project that you can download here (C# though):
http://www.codestrider.com/blog/read/AESFileEncryptorWithRSAEncryptedKeys.aspx
The code basically uses AES for encryption of binary data and then RSA encrypts the Key and the IV using an X509Certificate. So, as long as the private key certificate is available, the Key and IV can be decrypted, and then in turn the AES encrypted data can be decrypted ..
You could set up your certificate stores so that the 'encryptor' only has access to the public key certificate, while the 'decryptor' has access to the private key.
This allows you to encrypt using different Key and IV each time and avoid hardcoding anything.. which I believe is more secure. There should be nothing in your source code that would easily allow someone to decrypt your data - and if your system was ever compromised, you would only need to swap out the certificates with new ones. No need to recompile the application with new hardcoded values.. :)
The sample code may be slightly different from your intended use, but I think the technique and some of the code might be useful to you.
Below you'll find a class that provides AES Encryption/Decryption methods that explicitly provide URL-friendly strings for use in applications like yours. It also has the methods that work with byte arrays.
NOTE: you should use different values in the Key and Vector arrays! You wouldn't want someone to figure out your keys by just assuming that you used this code as-is! All you have to do is change some of the numbers (must be <= 255) in the Key and Vector arrays.
Using it is easy: just instantiate the class and then call (usually) EncryptToString(string StringToEncrypt) and DecryptString(string StringToDecrypt) as methods. It couldn't be any easier (or more secure) once you have this class in place.
using System;
using System.Data;
using System.Security.Cryptography;
using System.IO;
public class SimpleAES
{
// Change these keys
private byte[] Key = { 123, 217, 19, 11, 24, 26, 85, 45, 114, 184, 27, 162, 37, 112, 222, 209, 241, 24, 175, 144, 173, 53, 196, 29, 24, 26, 17, 218, 131, 236, 53, 209 };
private byte[] Vector = { 146, 64, 191, 111, 23, 3, 113, 119, 231, 121, 2521, 112, 79, 32, 114, 156 };
private ICryptoTransform EncryptorTransform, DecryptorTransform;
private System.Text.UTF8Encoding UTFEncoder;
public SimpleAES()
{
//This is our encryption method
RijndaelManaged rm = new RijndaelManaged();
//Create an encryptor and a decryptor using our encryption method, key, and vector.
EncryptorTransform = rm.CreateEncryptor(this.Key, this.Vector);
DecryptorTransform = rm.CreateDecryptor(this.Key, this.Vector);
//Used to translate bytes to text and vice versa
UTFEncoder = new System.Text.UTF8Encoding();
}
/// -------------- Two Utility Methods (not used but may be useful) -----------
/// Generates an encryption key.
static public byte[] GenerateEncryptionKey()
{
//Generate a Key.
RijndaelManaged rm = new RijndaelManaged();
rm.GenerateKey();
return rm.Key;
}
/// Generates a unique encryption vector
static public byte[] GenerateEncryptionVector()
{
//Generate a Vector
RijndaelManaged rm = new RijndaelManaged();
rm.GenerateIV();
return rm.IV;
}
/// ----------- The commonly used methods ------------------------------
/// Encrypt some text and return a string suitable for passing in a URL.
public string EncryptToString(string TextValue)
{
return ByteArrToString(Encrypt(TextValue));
}
/// Encrypt some text and return an encrypted byte array.
public byte[] Encrypt(string TextValue)
{
//Translates our text value into a byte array.
Byte[] bytes = UTFEncoder.GetBytes(TextValue);
//Used to stream the data in and out of the CryptoStream.
MemoryStream memoryStream = new MemoryStream();
/*
* We will have to write the unencrypted bytes to the stream,
* then read the encrypted result back from the stream.
*/
#region Write the decrypted value to the encryption stream
CryptoStream cs = new CryptoStream(memoryStream, EncryptorTransform, CryptoStreamMode.Write);
cs.Write(bytes, 0, bytes.Length);
cs.FlushFinalBlock();
#endregion
#region Read encrypted value back out of the stream
memoryStream.Position = 0;
byte[] encrypted = new byte[memoryStream.Length];
memoryStream.Read(encrypted, 0, encrypted.Length);
#endregion
//Clean up.
cs.Close();
memoryStream.Close();
return encrypted;
}
/// The other side: Decryption methods
public string DecryptString(string EncryptedString)
{
return Decrypt(StrToByteArray(EncryptedString));
}
/// Decryption when working with byte arrays.
public string Decrypt(byte[] EncryptedValue)
{
#region Write the encrypted value to the decryption stream
MemoryStream encryptedStream = new MemoryStream();
CryptoStream decryptStream = new CryptoStream(encryptedStream, DecryptorTransform, CryptoStreamMode.Write);
decryptStream.Write(EncryptedValue, 0, EncryptedValue.Length);
decryptStream.FlushFinalBlock();
#endregion
#region Read the decrypted value from the stream.
encryptedStream.Position = 0;
Byte[] decryptedBytes = new Byte[encryptedStream.Length];
encryptedStream.Read(decryptedBytes, 0, decryptedBytes.Length);
encryptedStream.Close();
#endregion
return UTFEncoder.GetString(decryptedBytes);
}
/// Convert a string to a byte array. NOTE: Normally we'd create a Byte Array from a string using an ASCII encoding (like so).
// System.Text.ASCIIEncoding encoding = new System.Text.ASCIIEncoding();
// return encoding.GetBytes(str);
// However, this results in character values that cannot be passed in a URL. So, instead, I just
// lay out all of the byte values in a long string of numbers (three per - must pad numbers less than 100).
public byte[] StrToByteArray(string str)
{
if (str.Length == 0)
throw new Exception("Invalid string value in StrToByteArray");
byte val;
byte[] byteArr = new byte[str.Length / 3];
int i = 0;
int j = 0;
do
{
val = byte.Parse(str.Substring(i, 3));
byteArr[j++] = val;
i += 3;
}
while (i < str.Length);
return byteArr;
}
// Same comment as above. Normally the conversion would use an ASCII encoding in the other direction:
// System.Text.ASCIIEncoding enc = new System.Text.ASCIIEncoding();
// return enc.GetString(byteArr);
public string ByteArrToString(byte[] byteArr)
{
byte val;
string tempStr = "";
for (int i = 0; i <= byteArr.GetUpperBound(0); i++)
{
val = byteArr[i];
if (val < (byte)10)
tempStr += "00" + val.ToString();
else if (val < (byte)100)
tempStr += "0" + val.ToString();
else
tempStr += val.ToString();
}
return tempStr;
}
}
Markt pointed out that Rijndael uses the AES encryption algorithm. Since a managed implementation ships with the .net framework (and has since at least 1.1), using it should satisfy the OP.
The API docs have a pretty straightforward example of using Rijndael as an encryption and decryption stream.
If you've got a way to get the shared secret (e.g., the private key) to the other website then you might be able to get away with using plain old symmetric encryption (no public key, both sides know the IV and private key). This is especially the case if your brain is the "insecure channel" across which the key is shared (e.g., you administer both websites). :)
Have a look at "Keep Your Data Secure
with the New Advanced Encryption
Standard". An AES implementation
doesn't ship with the .NET framework
but it links to a custom
implementation (AES.exe).
1:
http://msdn.microsoft.com/en-us/magazine/cc164055.aspx

How do I get an already (basic) authenticated context to call a web service behind the same authentication?

I have a site behind basic authentication (IIS6).
Part of this site calls a web service that is also part of the site and thus behind basic authentication as well.
However, when this happens the calling code receives a 401 Authentication Error.
I've tried a couple of things, with the general recommendation being code like this:
Service.ServiceName s = new Service.ServiceName();
s.PreAuthenticate = true;
s.Credentials = System.Net.CredentialCache.DefaultCredentials;
s.Method("Test");
However, this does not seem to resolve my problem.
Any advice?
Edit
This seems to be a not uncommon issue but so far I have found no solutions.
Here is one thread on the topic.
Solution: (I am almost certain this will help someone)
See this link for the source of this solution in VB (thanks jshardy!), all I did was convert to C#.
NB: You must be using ONLY basic authentication on IIS for this to work, but it can probably be adapted. You also need to pass a Page instance in, or at least the Request.ServerVariables property (or use 'this' if called from a Page code-behind directly). I'd tidy this up and probably remove the use of references but this is a faithful translation of the original solution and you can make any amendments necessary.
public static void ServiceCall(Page p)
{
LocalServices.ServiceName s = new LocalServices.ServiceName();
s.PreAuthenticate = true; /* Not sure if required */
string username = "";
string password = "";
string domain = "";
GetBasicCredentials(p, ref username, ref password, ref domain);
s.Credentials = new NetworkCredential(username, password, domain);
s.ServiceMethod();
}
/* Converted from: http://forums.asp.net/t/1172902.aspx */
private static void GetBasicCredentials(Page p, ref string rstrUser, ref string rstrPassword, ref string rstrDomain)
{
if (p == null)
{
return;
}
rstrUser = "";
rstrPassword = "";
rstrDomain = "";
rstrUser = p.Request.ServerVariables["AUTH_USER"];
rstrPassword = p.Request.ServerVariables["AUTH_PASSWORD"];
SplitDomainUserName(rstrUser, ref rstrDomain, ref rstrUser);
/* MSDN KB article 835388
BUG: The Request.ServerVariables("AUTH_PASSWORD") object does not display certain characters from an ASPX page */
string lstrHeader = p.Request.ServerVariables["HTTP_AUTHORIZATION"];
if (!string.IsNullOrEmpty(lstrHeader) && lstrHeader.StartsWith("Basic"))
{
string lstrTicket = lstrHeader.Substring(6);
lstrTicket = System.Text.Encoding.Default.GetString(Convert.FromBase64String(lstrTicket));
rstrPassword = lstrTicket.Substring((lstrTicket.IndexOf(":") + 1));
}
/* At least on my XP Pro machine AUTH_USER is not set (probably because we're using Forms authentication
But if the password is set (either by AUTH_PASSWORD or HTTP_AUTHORIZATION)
then we can use LOGON_USER*/
if (string.IsNullOrEmpty(rstrUser) && !string.IsNullOrEmpty(rstrPassword))
{
rstrUser = p.Request.ServerVariables["LOGON_USER"];
SplitDomainUserName(rstrUser, ref rstrDomain, ref rstrUser);
}
}
/* Converted from: http://forums.asp.net/t/1172902.aspx */
private static void SplitDomainUserName(string pstrDomainUserName, ref string rstrDomainName, ref string rstrUserName)
{
rstrDomainName = "";
rstrUserName = pstrDomainUserName;
int lnSlashPos = pstrDomainUserName.IndexOf("\\");
if (lnSlashPos > 0)
{
rstrDomainName = pstrDomainUserName.Substring(0, lnSlashPos);
rstrUserName = pstrDomainUserName.Substring(lnSlashPos + 1);
}
}
The Line:
s.Credentials = System.Net.CredentialCache.DefaultCredentials();
Maybe you should try :
s.Credentials = HttpContext.Current.User.Identity;

Resources