Java SecretKey and replicating it's behaviour in C# - c#

I am trying to replicate the encryption logic found in a Java library in a C# application.
The Java contains two methods which I have managed to replicate in C#. I get the same results in each program for any set of data.
createKey(byte data1[], MessageDigest md);
createIV(byte data2[], MessageDigest md);
The logic to generate the key and IV in Java is as follows:
public Cipher getCipher(byte[] password) {
MessageDigest md = MessageDigest.getInstance("SHA-1");
byte keyData[] = createKey(byte[] password, md);
SecretKey secretKey =
SecretKeyFactory.getInstance("DESede").
generateSecret(new DESedeKeySpec(keyData[]));
IVSpec ivspec = createIV(secretKey.getEncoded(), md);
Cipher cipher = Cipher.getInstance("DESede/CBC/PKCS5Padding");
cipher.init(1, secretKey, ivSpec, md);
return cipher;
}
Let's say I have the follow:
Java Key HEX: 9c 3a 79 df ba 49 86 0 ed 58 1 d8 9b a7 94 0 bb 3e 8f 80 4d 67 0 0
When I build the secretKey and then call secretKey.getEncoded() I get:
Java Encoded Key: : 9d 3b 79 df ba 49 86 1 ec 58 1 d9 9b a7 94 1 ba 3e 8f 80 4c 67 1 1
Because I don't know what the SecretKey is doing internally I don't know how to replicate this in C#.
My current C# code looks like this:
public static ICryptoTransform createCryptoTransform(String password)
{
ICryptoTransform ct = null;
byte[] keyData = createKey(password);
byte[] ivData = createInitialisationVector(keyData);
printByteArray("keyData", keyData);
printByteArray("ivData", ivData);
TripleDESCryptoServiceProvider tdcsp = new TripleDESCryptoServiceProvider();
tdcsp.Key = keyData; / This seems to be ignored by CreateEncryptor method below
tdcsp.KeySize = 192;
tdcsp.IV = ivData; // This seems to be ignored by CreateEncryptor method below
tdcsp.Mode = CipherMode.CBC;
tdcsp.Padding = PaddingMode.PKCS7; // PKCS5 and PKCS7 provide the same padding scheme
ct = tdcsp.CreateEncryptor(keyData, ivData);
return ct;
}
As you can see, I'm using the ivData[] created from the unencoded key.
Everything works, that is I get the same encrypted result, if I pass the same IV data in when creating the encryptor but unfortunately I cannot modify how it generates it's IVSpec.
What is SecretKey doing internally and how do I replicate this in C#?

DES (and DESede) both derive 56 bits of key material from 64 bit input. The remaining 8 bits are used as parity check bits here. The source code shows you how Java handles this, you can apply the same in C#.
See also the section at the beginning of FIPS 46-3.

Related

Order of bytes after BitArray to byte[] convertation

I'm, trying to figure out bytes order after convertation from BitArray to byte[].
Firstly, here is the BitArray content:
BitArray encoded = huffmanTree.Encode(input);
foreach (bool bit in encoded)
{
Console.Write((bit ? 1 : 0));
}
Console.WriteLine();
Output:
Encoded: 000001010110101011111111
Okay, so if we convert these binary to Hex manually we will get: 05 6A FF
However, when I am using convertation in C#, here is what I get:
BitArray encoded = huffmanTree.Encode(input);
byte[] bytes = new byte[encoded.Length / 8 + (encoded.Length % 8 == 0 ? 0 : 1)];
encoded.CopyTo(bytes, 0);
string StringByte = BitConverter.ToString(bytes);
Console.WriteLine(StringByte); // just to check the Hex
Output:
A0-56-FF
Nevertheless, as I have mentioned, it should be 05 6A FF. Please help me to understand why is that so.

RSA Encrypt Handle invalid issue

I am trying to encrypt a text and the decrypt it using public private key pair. I need the keys to handled as string. So I convert these key from string to RSAParameters. But when i try to encrypt the text, it throws me an error as "The handle is not valid".
static RSAParameters _publicKey = new RSAParameters();
static RSAParameters _privateKey = new RSAParameters();
static string _strPublicKey = "-----BEGIN PUBLIC KEY-----\r\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsG5sBbnH7gXkrExzNOeK\r\nEjoRDHUH5uU+crH52Z1uXCEx8LiFow8RwrvZGqjYXgBwxzqOQwHJt3utoNVY0niP\r\nHjfPXwKTk79PkeET/mtRar1gEcCOr0/hgHxT3YGlQLw2ugVIulMzlUBRY4rceNv3\r\nEiSZ+4cnO04hJ6UiftrCfwTe6q9Hadp6B6SX+N9hgcHhRX4iR/VYUf/6cvN+NAgb\r\ntuF0Dk61C6ulh2Gvdj2TBCLaq1LPF5H+ghrRxjK/Zn6MG9BW2ju9g8zYKuufaaaM\r\ndbTmN+Z4f27nEOxFq5wWqeaWl53yrMia6xnOi7vtU8zcwBL7jSLgwrkyO8LabCdz\r\neQIDAQAB\r\n-----END PUBLIC KEY-----";
static string _strPrivateKey = "-----BEGIN RSA PRIVATE KEY-----\r\nMIIEogIBAAKCAQEAsG5sBbnH7gXkrExzNOeKEjoRDHUH5uU+crH52Z1uXCEx8LiF\r\now8RwrvZGqjYXgBwxzqOQwHJt3utoNVY0niPHjfPXwKTk79PkeET/mtRar1gEcCO\r\nr0/hgHxT3YGlQLw2ugVIulMzlUBRY4rceNv3EiSZ+4cnO04hJ6UiftrCfwTe6q9H\r\nadp6B6SX+N9hgcHhRX4iR/VYUf/6cvN+NAgbtuF0Dk61C6ulh2Gvdj2TBCLaq1LP\r\nF5H+ghrRxjK/Zn6MG9BW2ju9g8zYKuufaaaMdbTmN+Z4f27nEOxFq5wWqeaWl53y\r\nrMia6xnOi7vtU8zcwBL7jSLgwrkyO8LabCdzeQIDAQABAoIBAA2z5cvkC/UenA4N\r\nufzn5r9Xpy9Sf5SdRWZfEEqogYPCSECr9CUf7H81W71IU9WpLxkqIRZvMx1/C5Ms\r\nPsPJ/UOZjg+RAak9+I4Z7xWZfC9QGgAG9o4DJD54aYMQqKcIdy+nbWibQaxb3HZg\r\nuJLicqQEF7mDW7atcMHFf5JepzB6LO7u9mfgR03uHQh6r6ym27BTGwssSmEeOeiA\r\n+tOPEhCsbZMSs5+8aGMoV08OqjscytQCWDY8rwA8ZE/qis+cNxKo0OluRTde68mH\r\nbr42CZpNJNulhg4mZyxtrtC+D13VcRpFeKW7WbMBwEUJ8/liUBDvAPLB3Np46FsG\r\njcZfFmkCgYEAtSk5HGEk+y8dyl+l62u7oir5IEZf8vNsMJ+CrpF1C8e6qShRe9uy\r\nJ5pN/4dBb2thknOLsaw6K2qYGNkH3TYSpHusW7v4Iuy6ONmzXHCxcgconzCWJ0HI\r\nLWnYRZAHv8PdOxdFqukjLqFOz6fEyIJ5Ayp+7qxg3QRmE7bwnEwWYK8CgYEA+VEC\r\nBUVUd8tHLqMyaYuv7HXQlQ3J01fobts98k+xqQtKsMUu4SZQ/5uQF8Wk3P6AAKiU\r\ngeD81Fpu4PINrRH97R0twnlWru1fHNSHNenuLASW+3I4l975PhZJc5AeV7VpRcnY\r\nyZCMAKO/XRpfgTEsv5HNfidUsYuJ/9epQzVy6FcCgYAs1V3f6x621ys9OTybrZbL\r\nBG2REjmOq7V7tw4lW7QmzTAhyuuXhoBpkqN4+KU2CNIl51iMCP6AXin0BEoQ8d/d\r\nOwolzbgUFJfll+LunqkbejAQbXrLjlkW/Bnc5U81oyhuBk1khbwCP0N82p01rix6\r\nnxq4wIpcSElm2aBkXeQv2wKBgCvewUhEJtTdhC0Esn44AkDNimJwBq+VrGS1V3Un\r\n6M8iGYZ5bAJaR65ypSxJrvTkI4n6IAeqm1KShyg174ogvFnY5JBv4Xzub+oWy6QF\r\nAc/lDtw4ARVYOutd6JbZKT2twlRxbCAruzbxmV68oUmOaZ1b/pjQOury7tmCDVqy\r\nMQIJAoGAIqUiIAnKLBrXhfN0nqGt2iOnRl31Ef3p/pTNwhUBdJphi/zlE9JHTI2Q\r\nCiLCGyJTpr3FoPjIJZ2P+fRrB3FmVuGNVZw5s8g4ouuDWNyCL/upVwU84eAWMu7P\r\nDsUL5Ia5W7/Cm7d0/nqchUCkRslIsH+bfGj0NZ7qcE5H1D8Ee0A=\r\n-----END RSA PRIVATE KEY-----";
static void Main(string[] args)
{
var message = "Hello World!";
var data = Encrypt(message);
var res = Decrypt(data);
}
private static RSAParameters GetRSAParameters(string pPublicKey)
{
byte[] lDer;
//Set RSAKeyInfo to the public key values.
int lBeginStart = "-----BEGIN PUBLIC KEY-----".Length;
int lEndLenght = "-----END PUBLIC KEY-----".Length;
string KeyString = pPublicKey.Substring(lBeginStart, (pPublicKey.Length - lBeginStart - lEndLenght));
lDer = Convert.FromBase64String(KeyString);
//Create a new instance of the RSAParameters structure.
RSAParameters lRSAKeyInfo = new RSAParameters();
lRSAKeyInfo.Modulus = GetModulus(lDer);
lRSAKeyInfo.Exponent = GetExponent(lDer);
return lRSAKeyInfo;
}
private static byte[] GetModulus(byte[] pDer)
{
//Size header is 29 bits
//The key size modulus is 128 bits, but in hexa string the size is 2 digits => 256
string lModulus = BitConverter.ToString(pDer).Replace("-", "").Substring(58, 256);
return StringHexToByteArray(lModulus);
}
private static byte[] GetExponent(byte[] pDer)
{
int lExponentLenght = pDer[pDer.Length - 3];
string lExponent = BitConverter.ToString(pDer).Replace("-", "").Substring((pDer.Length * 2) - lExponentLenght * 2, lExponentLenght * 2);
return StringHexToByteArray(lExponent);
}
public static byte[] StringHexToByteArray(string hex)
{
return Enumerable.Range(0, hex.Length)
.Where(x => x % 2 == 0)
.Select(x => Convert.ToByte(hex.Substring(x, 2), 16))
.ToArray();
}
public static string Encrypt(string mess)
{
string response = "";
var input = Encoding.UTF8.GetBytes(mess);
using (var rsa = new RSACryptoServiceProvider(1024))
{
rsa.PersistKeyInCsp = false;
rsa.ImportParameters(GetRSAParameters(_strPublicKey));
var decrypt = rsa.Encrypt(input, true);
response = Convert.ToBase64String(decrypt);
}
return response;
}
public static string Decrypt(string mess)
{
string response = "";
var input = Convert.FromBase64String(mess);
using (var rsa = new RSACryptoServiceProvider(2048))
{
rsa.PersistKeyInCsp = false;
rsa.ImportParameters(GetRSAParameters(_strPublicKey));
var decrypt = rsa.Decrypt(input, false);
response = Encoding.UTF8.GetString(decrypt);
}
return response;
}
Above is the code that I am using to encrypt and decrypt. After the conversion, I convert them to string and pass it to the decrypt function. Please advice
Your attempt to read the DER is definitely lacking. Possibly because you have a 2048-bit key and your "parser" at best only works for a 1024-bit key (though I'm having trouble deciding what key it could legitimately read).
Your public key hex is
30820122300D06092A864886F70D01010105000382010F003082010A02820101
00B06E6C05B9C7EE05E4AC4C7334E78A123A110C7507E6E53E72B1F9D99D6E5C
2131F0B885A30F11C2BBD91AA8D85E0070C73A8E4301C9B77BADA0D558D2788F
1E37CF5F029393BF4F91E113FE6B516ABD6011C08EAF4FE1807C53DD81A540BC
36BA0548BA5333954051638ADC78DBF7122499FB87273B4E2127A5227EDAC27F
04DEEAAF4769DA7A07A497F8DF6181C1E1457E2247F55851FFFA72F37E34081B
B6E1740E4EB50BABA58761AF763D930422DAAB52CF1791FE821AD1C632BF667E
8C1BD056DA3BBD83CCD82AEB9F69A68C75B4E637E6787F6EE710EC45AB9C16A9
E696979DF2ACC89AEB19CE8BBBED53CCDCC012FB8D22E0C2B9323BC2DA6C2773
790203010001
The DER breakdown (because I can't help myself) is
30 82 01 22
30 0D
06 09 2A 86 48 86 F7 0D 01 01 01
05 00
03
82 01 0F
00
30 82 01 0A
02 82 01 01
00B06E6C05B9C7EE05E4AC4C7334E78A123A110C7507E6E53E72B1F9D99D6E5C
2131F0B885A30F11C2BBD91AA8D85E0070C73A8E4301C9B77BADA0D558D2788F
1E37CF5F029393BF4F91E113FE6B516ABD6011C08EAF4FE1807C53DD81A540BC
36BA0548BA5333954051638ADC78DBF7122499FB87273B4E2127A5227EDAC27F
04DEEAAF4769DA7A07A497F8DF6181C1E1457E2247F55851FFFA72F37E34081B
B6E1740E4EB50BABA58761AF763D930422DAAB52CF1791FE821AD1C632BF667E
8C1BD056DA3BBD83CCD82AEB9F69A68C75B4E637E6787F6EE710EC45AB9C16A9
E696979DF2ACC89AEB19CE8BBBED53CCDCC012FB8D22E0C2B9323BC2DA6C2773
79
02 03
010001
Which is the ASN.1 equivalent of
SEQUENCE
SEQUENCE
OBJECT IDENTIFIER id-rsaEncryption
NULL
BIT STRING (0 bits unused) (wrapping)
SEQUENCE
INTEGER (positive) 0xB06E...7379
INTEGER 0x010001
Your modulus extractor skips 29 bytes and reads the next 128. (Why it does byte[] => hex => substring => byte[] instead of doing new byte[size] and Buffer.BlockCopy is beyond me). The problem is, your key is a 2048-bit key (the modulus length is 0x0101, and once we remove the sign-padding byte it's 0x0100 bytes long, with the high bit set, so 256*8 = 2048 bit modulus). So you need to have read 256 bytes. And, worse, because the payload is bigger than expected, it doesn't start at the offset you think it does. So where you should end up with B0 6E 6C ... 27 73 79 (after skipping the padding byte) you ended up with 82 01 01 00 *B0 6E 6C ... A5 22 7E (the asterisk marking the desired start of your payload). So, that's bad, and you are definitely not encrypting what you think you are. (The A5 22 7E sequence occurs on the line starting with 36BA, ending with 3 bytes left to go on that line)
What's worse is the GetExponent method. It seems to try to combine actual DER reading with presupposing what the value is. It should conclude 01 00 01, but it reads the first 01 of the payload as a length, then uses that length to count from the end. So the exponent value ends up being just 01. The RSA algorithm doesn't work with e=1, and what's happening is probably that a post-encryption self-test decided that the results were garbage, and the Win32 crypto library concluded that the problem is that the caller gave it something that wasn't a valid key handle, as opposed to a valid handle to an invalid key. In addition to not understanding the byte[] => hex => substring => byte[] pattern here I don't understand why this code decided that pDer[-3] (to use syntax from another language) was the length value. DER can only be read "left to right", and if it knew "the exponent is always 0x010001" then a) that'd be length-encoded at pDer[-4] b) just reading the last three bytes would be forgivable and c) given how many assumptions this code makes it would've made more sense to just blindly assume 0x010001.
So, you need to a) replace your parser with either a correct parser or at least a specialized one which functions at your desired keysize and b) never use that private key for anything important, since you just posted it to the Internet (and the private key variable wasn't even used in this problem).

C# and PHP ECDH not matching

I'm trying to generate a shared secret between a web server running PHP and a C# desktop application. I'm aware of the BouncyCastle library, but I'd prefer not having to use it since it's pretty huge.
I'm using phpecc and ECDiffieHellmanCng and trying to generate a shared secret between the two parties but I'm having issues with exporting/importing in C#.
It seems phpecc requires der/pem format in order to import a key, and ECDiffieHellmanCng doesn't seem to have any easy way to export in a compatible format.
Would I need to write my own pem/der encoder and decoder in order to do this or is there some alternative easier way?
Currently I'm doing the following in C#:
using (var ecdh = new ECDiffieHellmanCng())
{
ecdh.HashAlgorithm = CngAlgorithm.ECDiffieHellmanP384;
ecdh.KeyDerivationFunction = ECDiffieHellmanKeyDerivationFunction.Hash;
var encoded = EncodePem(ecdh.PublicKey.ToByteArray());
//... do something with encoded
}
private static string EncodePem(byte[] data)
{
var pemDat = new StringBuilder();
var chunk = new char[64];
pemDat.AppendLine("-----BEGIN PUBLIC KEY-----");
var encodedData = Convert.ToBase64String(data);
for (var i = 0; i < encodedData.Length; i += chunk.Length)
{
var index = 0;
while (index != chunk.Length && i + index < encodedData.Length)
{
chunk[index] = encodedData[i + index];
index++;
}
pemDat.AppendLine(new string(chunk));
}
pemDat.AppendLine("-----END PUBLIC KEY-----");
return pemDat.ToString();
}
Obviously the above is only doing the pem encoding, so on the php side it returns an error when it's trying to parse it:
Type: Runtime
Exception Message: Invalid data.
File: /.../vendor/mdanter/ecc/src/Serializer/PublicKey/Der/Parser.php
Line: 49
.NET Core 1.0 and .NET Framework 4.7 have the ECParameters struct to import/export keys. The ToByteArray() method you called is producing a CNG EccPublicBlob which has very little to do with the SEC-1 ECParameters format.
I'm going to assume that you wanted to use secp384r1/NIST P-384, even though you specified that as a hash algorithm. If you want some other curve, you'll need to do some translations.
The (.NET) ECParameters struct will only help you get started. Turning that into a file requires translating it into a PEM-encoded DER-encoded ASN.1-based structure. (But if you're sticking with NIST P-256/384/521, you can do it with the byte[] you currently have)
In SEC 1 v2.0 we get the following structures:
SubjectPublicKeyInfo ::= SEQUENCE {
algorithm AlgorithmIdentifier {{ECPKAlgorithms}} (WITH COMPONENTS {algorithm, parameters}),
subjectPublicKey BIT STRING
}
ECPKAlgorithms ALGORITHM ::= {
ecPublicKeyType |
ecPublicKeyTypeRestricted |
ecPublicKeyTypeSupplemented |
{OID ecdh PARMS ECDomainParameters {{SECGCurveNames}}} |
{OID ecmqv PARMS ECDomainParameters {{SECGCurveNames}}},
...
}
ecPublicKeyType ALGORITHM ::= {
OID id-ecPublicKey PARMS ECDomainParameters {{SECGCurveNames}}
}
ECDomainParameters{ECDOMAIN:IOSet} ::= CHOICE {
specified SpecifiedECDomain,
named ECDOMAIN.&id({IOSet}),
implicitCA NULL
}
An elliptic curve point itself is represented by the following type
ECPoint ::= OCTET STRING
whose value is the octet string obtained from the conversion routines given in Section 2.3.3.
Distilling this down to the relevant parts, you need to write
SEQUENCE (SubjectPublicKeyInfo)
SEQUENCE (AlgorithmIdentifier)
OBJECT IDENTIFIER id-ecPublicKey
OBJECT IDENTIFIER secp384r1 (or whatever named curve you're using)
BIT STRING
public key encoded as ECPoint
The AlgorithmIdentifier contains data that's fixed given you don't change the curve:
SEQUENCE (AlgorithmIdentifier)
30 xx [yy [zz]]
OBJECT IDENTIFIER id-ecPublicKey (1.2.840.10045.2.1)
06 07 2A 86 48 CE 3D 02 01
OBJECT IDENTIFIER secp384r1 (1.3.132.0.34)
06 05 2B 81 04 00 22
and we can now count how many bytes were in the payload: 16 (0x10), so we fill in the length:
30 10 06 07 2A 86 48 CE 3D 02 01 06 05 2B 81 04
00 22
The public key encoding that everyone understands is "uncompressed point", which is
04 th eb yt es of x. th eb yt es of y.
Turns out, that has a fixed size for a given curve, too, so unlike most things that are DER encoded, you can do this in one pass :). For secp384r1 the x and y coordinate are each 384 bit values, or (384 + 7)/8 == 48 bytes, so the ECPoint is 48 + 48 + 1 == 97 (0x61) bytes. Then it needs to be wrapped in a BIT STRING, which adds one payload byte and the length and tag. So, we get:
private static byte[] s_secp384r1PublicPrefix = {
// SEQUENCE (SubjectPublicKeyInfo, 0x76 bytes)
0x30, 0x76,
// SEQUENCE (AlgorithmIdentifier, 0x10 bytes)
0x30, 0x10,
// OBJECT IDENTIFIER (id-ecPublicKey)
0x06, 0x07, 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x02, 0x01,
// OBJECT IDENTIFIER (secp384r1)
0x06, 0x05, 0x2B, 0x81, 0x04, 0x00, 0x22,
// BIT STRING, 0x61 content bytes, 0 unused bits.
0x03, 0x62, 0x00,
// Uncompressed EC point
0x04,
}
...
using (ECDiffieHellman ecdh = ECDiffieHellman.Create())
{
ecdh.KeySize = 384;
byte[] prefix = s_secp384r1PublicPrefix;
byte[] derPublicKey = new byte[120];
Buffer.BlockCopy(prefix, 0, derPublicKey, 0, prefix.Length);
byte[] cngBlob = ecdh.PublicKey.ToByteArray();
Debug.Assert(cngBlob.Length == 104);
Buffer.BlockCopy(cngBlob, 8, derPublicKey, prefix.Length, cngBlob.Length - 8);
// Now move it to PEM
StringBuilder builder = new StringBuilder();
builder.AppendLine("-----BEGIN PUBLIC KEY-----");
builder.AppendLine(
Convert.ToBase64String(derPublicKey, Base64FormattingOptions.InsertLineBreaks));
builder.AppendLine("-----END PUBLIC KEY-----");
Console.WriteLine(builder.ToString());
}
Running the output I got from that into OpenSSL:
$ openssl ec -pubin -text -noout
read EC key
(paste)
-----BEGIN PUBLIC KEY-----
MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEwpbxYmcsNvr14D8k+0VQCkSY4WCV/3V10AiIq7sFdmUX
9+0DMuuLDmcKjL1ZFEFk0yHCPpY+pdkYtzPwE+dsApCPT3Ljk0AxHQBTSo4yjwsElMoA4Mtp8Qdo
LZD1Nx6v
-----END PUBLIC KEY-----
Private-Key: (384 bit)
pub:
04:c2:96:f1:62:67:2c:36:fa:f5:e0:3f:24:fb:45:
50:0a:44:98:e1:60:95:ff:75:75:d0:08:88:ab:bb:
05:76:65:17:f7:ed:03:32:eb:8b:0e:67:0a:8c:bd:
59:14:41:64:d3:21:c2:3e:96:3e:a5:d9:18:b7:33:
f0:13:e7:6c:02:90:8f:4f:72:e3:93:40:31:1d:00:
53:4a:8e:32:8f:0b:04:94:ca:00:e0:cb:69:f1:07:
68:2d:90:f5:37:1e:af
ASN1 OID: secp384r1
NIST CURVE: P-384

PHP SHA1 vs PasswordDeriveBytes SHA1 - differing lengths

Having read around on this issue on these posts (to name but two)...
Encrypting / Decrypting in vb.net does not always return the same value
C# PasswordDeriveBytes Confusion
...I was wondering if anyone knew the method that Microsoft use to key stretch their output?
To integrate with a system written in C# by someone else, I need to generate a large hash in PHP. The code that generates their SHA1 hash generates a 32 byte output, rather than the standard 20 byte SHA1 output. So has anyone cracked how Microsoft fill up the remaining 12 bytes? I've tried lots of combinations of appending the a SHA1 of the salt, password etc. as well as appending on previous iterations, but nothing seems to produce Microsoft's output.
I'm slightly concerned by this comment on https://stackoverflow.com/a/13482133/4346051...
"PasswordDeriveBytes uses an unknown, proprietary, non-deterministic, broken,
cryptographically insecure method of key stretching for PasswordDeriveBytes"
Sample code as follows...
The C# code I need to replicate in PHP in order to produce the correct hash for integration with third party software:
string sPassword = "iudF98yfh9aRfhanifakdfn0a4ut-a8J789jasdpasd=";
string sSalt = "lYU7m+MDCvVWVQyuLIX3og==";
int iLength = 256 / 8;
byte[] saltInBytes = Encoding.ASCII.GetBytes(sSalt);
PasswordDeriveBytes password = new PasswordDeriveBytes(sPassword, saltInBytes, "SHA1", 2);
byte[] keyBytes = password.GetBytes(iLength);
Console.WriteLine("\nkeyBytes = \n");
foreach (byte value in keyBytes)
{
Console.WriteLine(value);
}
...which outputs...
0
176
172
127
35
113
212
85
123
19
71
65
23
127
84
165
163
225
80
207
67
125
128
205
188
248
103
52
23
245
111
20
My PHP code for the same functionality is...
$sPassword = "iudF98yfh9aRfhanifakdfn0a4ut-a8J789jasdpasd=";
$sSalt = "lYU7m+MDCvVWVQyuLIX3og==";
$iIterations = 2;
$iLength = 256 / 8;
// combine password with salt
$key = $sPassword.$sSalt;
// perform sha1 how ever many times, using raw binary output instead of hex
for($i = 1; $i <= $iIterations; $i++) {
$key = sha1($key, true);
}
// get the iLength number of chars
$key = substr($key, 0, $iLength);
$aKeyBytes = unpack('C*', $key);
print_r($aKeyBytes);
...and this outputs...
Array
(
[1] => 0
[2] => 176
[3] => 172
[4] => 127
[5] => 35
[6] => 113
[7] => 212
[8] => 85
[9] => 123
[10] => 19
[11] => 71
[12] => 65
[13] => 23
[14] => 127
[15] => 84
[16] => 165
[17] => 163
[18] => 225
[19] => 80
[20] => 207
)
...but as you can see, we're 12 "bytes" short.
Unfortunately, it has to be replicated - I can't amend the C# to use a better method - I have no control over that side - my PHP code has to generate it
I needed more or less the same thing - data encrypted using AES256-CBC in PHP that I needed to be able to decrypt using VB code.
Took me a while to search the internet and test various code samples, but I was able to get it working using information from the following posts:
C# / VB code here
How do I convert this C# Rijndael encryption to PHP?
Kind regards,
Sven

StreamWriter does not emit BOM for UTF-7

Have a look at this very simple piece of code:
private static void WriteTestLine(string file, Encoding encoding)
{
// 'Hello, world!' in russian
const string testLine = "Здравствуй, мир!";
using (var streamWriter = new StreamWriter(path: file, append: false, encoding: encoding))
{
streamWriter.WriteLine(testLine);
}
using (var streamReader = new StreamReader(path: file, detectEncodingFromByteOrderMarks: true))
{
Console.WriteLine(streamReader.ReadLine());
}
}
WriteTestLine("utf8", new UTF8Encoding(true));
WriteTestLine("utf7", new UTF7Encoding(true));
WriteTestLine("utf7woOptionals", new UTF7Encoding(false));
This code produces the following output:
Здравствуй, мир!
+BBcENARABDAEMgRBBEIEMgRDBDk-, +BDwEOARA-!
+BBcENARABDAEMgRBBEIEMgRDBDk-, +BDwEOARAACE-
As you can see, StreamReader was unable to detect UTF-7 encoding correctly. Indeed, if you open both UTF-7 files with a hex editor, in both cases the first four bytes will look like this:
2B 42 42 63
But Wikipedia states that UTF-7 should have a BOM that looks like one of the following:
2B 2F 76 38
2B 2F 76 39
2B 2F 76 2B
2B 2F 76 2F
2B 2F 76 38 2D
So I guess that .NET's StreamWriter is unable to work correctly with UTF-7. That seems odd. Do i miss something? How can I force StreamWriter to emit BOM in UTF-7 files?
P.S. By the way:
Encoding.UTF7.GetPreamble();
produces an empty array

Categories

Resources