Xor-el / CryptoLib4Pascal

Crypto for Modern Object Pascal
MIT License
213 stars 65 forks source link

SSH ECDH problem #20

Closed terrylao closed 3 years ago

terrylao commented 3 years ago

Describe the bug i got this information from PUTTY

dumped information :

Event Log: Doing ECDH key exchange with curve Curve25519 and hash SHA-256 (unaccelerated)

Event Log: Doing ECDH my PUB key :

length 32

423EBB48BE9425B867005C4F269C369CCA11D514C30A7276C8E3193DC2FAD355

Event Log: my Private Key:

length of int 32

B81895A577E387CBD485E41D40FDF838EA087CA0F80895E00B2CBC29ECDAD57A

Event Log: get from packget: hostkeydata :

length 149

000000077373682D72736100000001230000008100C7576DF22171A80402819B89F3D7A9F3AFC4685F048614351ACBB1446DD2D273BA910D0C4DC7CF2583AA676DDA653A9C17DD6F5DA010BEC5C8E25B9117694276D82E078922A785B86E90D78DDFB383158381E8A3F99B97018F095EDC374F2EACE724222C97629CD19E6A35F25018D425A23205228171C08E8183F3A43E1DFABD

Event Log: Doing ECDH get my PUB key :

length 32

423EBB48BE9425B867005C4F269C369CCA11D514C30A7276C8E3193DC2FAD355

Event Log: get from packget: remote pub key :

length 32

00D1F0E49F1FC6F6C69658A504DFD37EF4E7736BE32E4E119531F28091004C5E

Event Log: get from packget: remote pub key to mpint

length of int 32

00D1F0E49F1FC6F6C69658A504DFD37EF4E7736BE32E4E119531F28091004C5E

Event Log: calculated K:

length of int 32

5C83053209F541B4DD1135D14690312E2C3396EAA8ABC48068854E3CBCDAFC07


the shared secret should be : 5C83053209F541B4DD1135D14690312E2C3396EAA8ABC48068854E3CBCDAFC07

To Reproduce but my test code below do not get the same shared secret, do i miss something?

procedure testCurve25519; var s:string; priv,remotepub,mypub,encryptkey,tmp:TBytes; n:TCryptoLibUInt32Array; var kpGen: IAsymmetricCipherKeyPairGenerator; kpA, kpB: IAsymmetricCipherKeyPair; agreeA, agreeB: IX25519Agreement; secretA, secretB: TBytes; FRandom: ISecureRandom; privateKey: IX25519PrivateKeyParameters; publicKey,kpBpublicKey: IX25519PublicKeyParameters; begin FRandom := TSecureRandom.Create(); kpGen := TX25519KeyPairGenerator.Create() as IX25519KeyPairGenerator; kpGen.Init(TX25519KeyGenerationParameters.Create(FRandom) as IX25519KeyGenerationParameters); //if (not AreEqual(secretA, secretB)) then //begin // Fail('X25519 agreement failed'); //end; s:='B81895A577E387CBD485E41D40FDF838EA087CA0F80895E00B2CBC29ECDAD57A'; priv := THex.Decode(s); privateKey := TX25519PrivateKeyParameters.Create(priv,0); publicKey := privateKey.GeneratePublicKey(); kpA := TAsymmetricCipherKeyPair.Create(publicKey, privateKey); tmp:=publicKey.GetEncoded(); s:=THex.Encode(tmp); writeln(StdOut,'my pub from priv:',s); s:='00D1F0E49F1FC6F6C69658A504DFD37EF4E7736BE32E4E119531F28091004C5E'; remotepub := THex.Decode(s); kpBpublicKey:=TX25519PublicKeyParameters.Create(remotepub,0); agreeA := TX25519Agreement.Create(); agreeA.Init(kpA.Private); System.SetLength(secretA, agreeA.AgreementSize); agreeA.CalculateAgreement(kpBpublicKey, secretA, 0); s:=THex.Encode(secretA); writeln(StdOut,'secretA:',s); System.SetLength(encryptkey, TX25519.PointSize); System.SetLength(mypub, TX25519.PointSize); writeln(StdOut,'test private to public'); TX25519.ScalarMultBase(priv, 0, mypub, 0); s:=THex.Encode(mypub); writeln(StdOut,'my pub:',s); TX25519.ScalarMult(priv, 0,remotepub , 0, encryptkey, 0); s:=THex.Encode(encryptkey); writeln(StdOut,'shared secret:',s);
end; OUTPUT:

my pub from priv:423EBB48BE9425B867005C4F269C369CCA11D514C30A7276C8E3193DC2FAD355

secretA:07FCDABC3C4E856880C4ABA8EA96332C2E319046D13511DDB441F5093205835C

test private to public

my pub:423EBB48BE9425B867005C4F269C369CCA11D514C30A7276C8E3193DC2FAD355

shared secret:07FCDABC3C4E856880C4ABA8EA96332C2E319046D13511DDB441F5093205835C

Expected behavior Shared secret should same as putty? I am making an SSH client.

Environment (please complete the following information):

Xor-el commented 3 years ago

Hi, as you indicated the algorithm used is " ECDH key exchange with curve Curve25519 and hash SHA-256 (unaccelerated)". trying to re-implement this without having access to the source code implementation of that algorithm or specification is like taking a stab in the dark. like for example, we don't know what the hash is used for, what data is hashed etc. I suggest you spend sometime trying to read up how openssh implemented theirs then we can put the pieces together using CryptoLib4Pascal.

terrylao commented 3 years ago

MY doubt is why your code and putty's code generate different shared key, with same input. so, i think, this should re-implement.

Xor-el commented 3 years ago

it seems you still don't understand what I am trying to explain. Putty does not use the default Curve25519 standard, it uses a variant that uses SHA-256 for it's internal operation. these are totally 2 different algorithms in a sense.

terrylao commented 3 years ago

i found the algorithm of what they said https://git.libssh.org/users/aris/libssh.git/tree/doc/curve25519-sha256@libssh.org.txt

4.3 Shared secret generation

The shared secret, k, is defined in SSH specifications to be a big integer. This number is calculated using the following procedure:

 X is the 32 bytes point obtained by the scalar multiplication of the other
 side's public key and the local private key scalar.

 The whole 32 bytes of the number X are then converted into a big integer k.
 This conversion follows the network byte order. This step differs from 
 RFC5656.
Xor-el commented 3 years ago

According to the link you provided, it is clearly indicated that their implementation differs from the defined standard (RFC5656). If you want to produce the same result as theirs, I have attached a code below that works as per their standard. However, do note that this is not the Official X25519 spec.

function ReverseArray(const origArray: TBytes): TBytes;
var
  i, h: integer;
begin
  h := High(origArray);
  SetLength(Result, Length(origArray));
  for i := 0 to h do
    Result[i] := origArray[h - i];
  end;

procedure testCurve25519;
var
  s: string;
  bigint: TBigInteger;
  priv, remotepub, mypub, encryptkey, tmp: TBytes;
  n: TCryptoLibUInt32Array;
var
  kpGen: IAsymmetricCipherKeyPairGenerator;
  kpA, kpB: IAsymmetricCipherKeyPair;
  agreeA, agreeB: IX25519Agreement;
  secretA, secretB: TBytes;
  FRandom: ISecureRandom;
  privateKey: IX25519PrivateKeyParameters;
  publicKey, kpBpublicKey: IX25519PublicKeyParameters;
begin
  FRandom := TSecureRandom.Create();
  kpGen := TX25519KeyPairGenerator.Create() as IX25519KeyPairGenerator;
  kpGen.Init(TX25519KeyGenerationParameters.Create(FRandom)
    as IX25519KeyGenerationParameters);
  // if (not AreEqual(secretA, secretB)) then
  // begin
  // Fail('X25519 agreement failed');
  // end;
  s := 'B81895A577E387CBD485E41D40FDF838EA087CA0F80895E00B2CBC29ECDAD57A';
  priv := THex.Decode(s);
  privateKey := TX25519PrivateKeyParameters.Create(priv, 0);
  publicKey := privateKey.GeneratePublicKey();
  kpA := TAsymmetricCipherKeyPair.Create(publicKey, privateKey);
  tmp := publicKey.GetEncoded();
  s := THex.Encode(tmp);
  writeln(Format('my pub from priv: %s', [s]));
  s := '00D1F0E49F1FC6F6C69658A504DFD37EF4E7736BE32E4E119531F28091004C5E';
  remotepub := THex.Decode(s);
  kpBpublicKey := TX25519PublicKeyParameters.Create(remotepub, 0);
  agreeA := TX25519Agreement.Create();
  agreeA.Init(kpA.Private);
  System.SetLength(secretA, agreeA.AgreementSize);
  agreeA.CalculateAgreement(kpBpublicKey, secretA, 0);
  bigint := TBigInteger.Create(secretA);
  writeln(Format('secretA: %s', [THex.Encode(ReverseArray(bigint.ToByteArray()))]));
end;
Xor-el commented 3 years ago

please reopen if the above solution does not resolve your issue.