Java code didn't seem to import properly as it doesn't compile

I imported this java code:

import java.io.UnsupportedEncodingException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;

/**
 * RFC 2898 password derivation compatible with .NET Rfc2898DeriveBytes class. 
 */
public class Rfc2898DeriveBytes {

    private Mac _hmacSha1;
    private byte[] _salt;
    private int _iterationCount;

    private byte[] _buffer = new byte[20];
    private int _bufferStartIndex = 0;
    private int _bufferEndIndex = 0;
    private int _block = 1;

    
    /**
     * Creates new instance.
     * @param password The password used to derive the key.
     * @param salt The key salt used to derive the key.
     * @param iterations The number of iterations for the operation.
     * @throws NoSuchAlgorithmException HmacSHA1 algorithm cannot be found.
     * @throws InvalidKeyException Salt must be 8 bytes or more. -or- Password cannot be null.
     */
    public Rfc2898DeriveBytes(byte[] password, byte[] salt, int iterations) throws NoSuchAlgorithmException, InvalidKeyException {
    	if ((salt == null) || (salt.length < 8)) { throw new InvalidKeyException("Salt must be 8 bytes or more."); }
    	if (password == null) { throw new InvalidKeyException("Password cannot be null."); }
        this._salt = salt;
        this._iterationCount = iterations;
        this._hmacSha1 = Mac.getInstance("HmacSHA1");
        this._hmacSha1.init(new SecretKeySpec(password, "HmacSHA1"));
    }
    
    /**
     * Creates new instance.
     * @param password The password used to derive the key.
     * @param salt The key salt used to derive the key.
     * @param iterations The number of iterations for the operation.
     * @throws NoSuchAlgorithmException HmacSHA1 algorithm cannot be found.
     * @throws InvalidKeyException Salt must be 8 bytes or more. -or- Password cannot be null.
     * @throws UnsupportedEncodingException UTF-8 encoding is not supported. 
     */
    public Rfc2898DeriveBytes(String password, byte[] salt, int iterations) throws InvalidKeyException, NoSuchAlgorithmException, UnsupportedEncodingException  {
    	this(password.getBytes("UTF8"), salt, iterations);
    }

    /**
     * Creates new instance.
     * @param password The password used to derive the key.
     * @param salt The key salt used to derive the key.
     * @throws NoSuchAlgorithmException HmacSHA1 algorithm cannot be found.
     * @throws InvalidKeyException Salt must be 8 bytes or more. -or- Password cannot be null.
     * @throws UnsupportedEncodingException UTF-8 encoding is not supported. 
     */
    public Rfc2898DeriveBytes(String password, byte[] salt) throws NoSuchAlgorithmException, InvalidKeyException, UnsupportedEncodingException {
    	this(password, salt, 0x3e8);
    }


    /**
     * Returns a pseudo-random key from a password, salt and iteration count.
     * @param count Number of bytes to return.
     * @return Byte array.
     */
    public byte[] getBytes(int count) {
        byte[] result = new byte[count];
        int resultOffset = 0;
        int bufferCount = this._bufferEndIndex - this._bufferStartIndex;

        if (bufferCount > 0) { //if there is some data in buffer
            if (count < bufferCount) { //if there is enough data in buffer
            	System.arraycopy(this._buffer, this._bufferStartIndex, result, 0, count);
                this._bufferStartIndex += count;
                return result;
            }
            System.arraycopy(this._buffer, this._bufferStartIndex, result, 0, bufferCount);
            this._bufferStartIndex = this._bufferEndIndex = 0;
            resultOffset += bufferCount;
        }

        while (resultOffset < count) {
            int needCount = count - resultOffset;
            this._buffer = this.func();
            if (needCount > 20) { //we one (or more) additional passes
            	System.arraycopy(this._buffer, 0, result, resultOffset, 20);
                resultOffset += 20;
            } else {
            	System.arraycopy(this._buffer, 0, result, resultOffset, needCount);
                this._bufferStartIndex = needCount;
                this._bufferEndIndex = 20;
                return result;
            }
        }
        return result;
    }

    
    private byte[] func() {
        this._hmacSha1.update(this._salt, 0, this._salt.length);
        byte[] tempHash = this._hmacSha1.doFinal(getBytesFromInt(this._block));

        this._hmacSha1.reset();
        byte[] finalHash = tempHash;
        for (int i = 2; i <= this._iterationCount; i++) {
            tempHash = this._hmacSha1.doFinal(tempHash);
            for (int j = 0; j < 20; j++) {
                finalHash[j] = (byte)(finalHash[j] ^ tempHash[j]);
            }
        }
        if (this._block == 2147483647) {
            this._block = -2147483648;
        } else {
            this._block += 1;
        }

        return finalHash;
    }

    private static byte[] getBytesFromInt(int i) {
    	return new byte[] { (byte)(i >>> 24), (byte)(i >>> 16), (byte)(i >>> 8), (byte)i };
    }
	
}

And I got this oxygene:

namespace SandW;
interface
uses
  java.io,
  java.security,
  javax.crypto,
  javax.crypto.spec;

type
    // {*
  // * RFC 2898 password derivation compatible with .NET Rfc2898DeriveBytes class. 
  // }
Rfc2898DeriveBytes = public class
  private
    var _hmacSha1: Mac;
    var _salt: array of Byte;
    var _iterationCount: Integer;
    var _buffer: array of Byte := new Byte[20];
    var _bufferStartIndex: Integer := 0;
    var _bufferEndIndex: Integer := 0;
    var _block: Integer := 1;
  public
    constructor(password: array of Byte; salt: array of Byte; iterations: Integer);
    constructor(password: String; salt: array of Byte; iterations: Integer);
    constructor(password: String; salt: array of Byte);
    method getBytes(count: Integer): array of Byte;
  private
    method func(): array of Byte;
    class method getBytesFromInt(i: Integer): array of Byte;
  end;

implementation

constructor Rfc2898DeriveBytes(password: array of Byte; salt: array of Byte; iterations: Integer);
begin
  if ((salt = nil) or (salt.length < 8)) then begin
    raise new InvalidKeyException('Salt must be 8 bytes or more.')
  end;
  if (password = nil) then begin
    raise new InvalidKeyException('Password cannot be null.')
  end;
  self._salt := salt;
  self._iterationCount := iterations;
  self._hmacSha1 := Mac.getInstance('HmacSHA1');
  self._hmacSha1.init(new SecretKeySpec(password, 'HmacSHA1'))
end;

constructor Rfc2898DeriveBytes(password: String; salt: array of Byte; iterations: Integer);
begin
  self(password.getBytes('UTF8'), salt, iterations)
end;

constructor Rfc2898DeriveBytes(password: String; salt: array of Byte);
begin
  self(password, salt, $3e8)
end;

method Rfc2898DeriveBytes.getBytes(count: Integer): array of Byte;
begin
  var &result: array of Byte := new Byte[count];
  var resultOffset: Integer := 0;
  var bufferCount: Integer := (self._bufferEndIndex - self._bufferStartIndex);
  if (bufferCount > 0) then begin
    // if there is some data in buffer
    if (count < bufferCount) then begin
      // if there is enough data in buffer
      System.arraycopy(self._buffer, self._bufferStartIndex, &result, 0, count);
      self._bufferStartIndex := self._bufferStartIndex + count;
      exit &result
    end;
    System.arraycopy(self._buffer, self._bufferStartIndex, &result, 0, bufferCount);
    self._bufferStartIndex := 0;
    self._bufferEndIndex := 0;;
    resultOffset := resultOffset + bufferCount
  end;
  while (resultOffset < count) do begin
    var needCount: Integer := (count - resultOffset);
    self._buffer := self.func();
    if (needCount > 20) then begin
      // we one (or more) additional passes
      System.arraycopy(self._buffer, 0, &result, resultOffset, 20);
      resultOffset := resultOffset + 20
    end
    else
begin
      System.arraycopy(self._buffer, 0, &result, resultOffset, needCount);
      self._bufferStartIndex := needCount;
      self._bufferEndIndex := 20;
      exit &result
    end
  end;
  exit &result
end;

method Rfc2898DeriveBytes.func(): array of Byte;
begin
  self._hmacSha1.update(self._salt, 0, self._salt.length);
  var tempHash: array of Byte := self._hmacSha1.doFinal(getBytesFromInt(self._block));
  self._hmacSha1.reset();
  var finalHash: array of Byte := tempHash;
  // TODO: Check continue in unwrapped java for loop 
begin
    var i: Integer := 2;
    while (i <= self._iterationCount) do begin
      tempHash := self._hmacSha1.doFinal(tempHash);
      // TODO: Check continue in unwrapped java for loop 
begin
        var j: Integer := 0;
        while (j < 20) do begin
          finalHash[j] := Byte((finalHash[j] xor tempHash[j]));
          {postfix}inc(j)
        end
      end;
      {postfix}inc(i)
    end
  end;
  if (self._block = 2147483647) then begin
    self._block := - 2147483648
  end
  else
begin
    self._block := self._block + 1
  end;
  exit finalHash
end;

class method Rfc2898DeriveBytes.getBytesFromInt(i: Integer): array of Byte;
begin
  exit array of Byte([Byte((i shr 24)), Byte((i shr 16)), Byte((i shr 8)), Byte(i)])
end;

end.

But that code gets these compiler errors:

Error	1	(E407) No overloaded constructor with these parameters for type "Rfc2898DeriveBytes", best matching overload is "constructor (password: array of nullable Byte; salt: array of nullable Byte; iterations: Integer)"	C:\Users\Mark\Documents\Visual Studio 2013\Projects\Encryption\consoleapplication7\ClassLibrary1\Rfc2898.pas	65	23	SandWEncryptDecrypt
Warning	2	(H3) parameter 1 is "String" should be "array of nullable Byte"	C:\Users\Mark\Documents\Visual Studio 2013\Projects\Encryption\consoleapplication7\ClassLibrary1\Rfc2898.pas	65	43	SandWEncryptDecrypt
Warning	3	(H3) parameter 2 is "array of SByte" should be "array of nullable Byte"	C:\Users\Mark\Documents\Visual Studio 2013\Projects\Encryption\consoleapplication7\ClassLibrary1\Rfc2898.pas	65	53	SandWEncryptDecrypt
Error	4	(E62) Type mismatch, cannot assign "array of nullable Byte" to "array of SByte"	C:\Users\Mark\Documents\Visual Studio 2013\Projects\Encryption\consoleapplication7\ClassLibrary1\Rfc2898.pas	68	19	SandWEncryptDecrypt
Error	5	(E407) No overloaded constructor with these parameters for type "SecretKeySpec", best matching overload is "constructor (arg1: array of SByte; arg2: String)"	C:\Users\Mark\Documents\Visual Studio 2013\Projects\Encryption\consoleapplication7\ClassLibrary1\Rfc2898DeriveBytes1.pas	45	27	SandWEncryptDecrypt
Warning	6	(H3) parameter 1 is "array of nullable Byte" should be "array of SByte"	C:\Users\Mark\Documents\Visual Studio 2013\Projects\Encryption\consoleapplication7\ClassLibrary1\Rfc2898DeriveBytes1.pas	45	41	SandWEncryptDecrypt
Error	7	(E105) Member "self" on type "Rfc2898DeriveBytes" is a variable but is used as a method	C:\Users\Mark\Documents\Visual Studio 2013\Projects\Encryption\consoleapplication7\ClassLibrary1\Rfc2898DeriveBytes1.pas	50	3	SandWEncryptDecrypt
Warning	8	(N4) Type "Rfc2898DeriveBytes" was declared here	C:\Users\Mark\Documents\Visual Studio 2013\Projects\Encryption\consoleapplication7\ClassLibrary1\Rfc2898DeriveBytes1.pas	13	1	SandWEncryptDecrypt
Error	9	(E105) Member "self" on type "Rfc2898DeriveBytes" is a variable but is used as a method	C:\Users\Mark\Documents\Visual Studio 2013\Projects\Encryption\consoleapplication7\ClassLibrary1\Rfc2898DeriveBytes1.pas	55	3	SandWEncryptDecrypt
Warning	10	(N4) Type "Rfc2898DeriveBytes" was declared here	C:\Users\Mark\Documents\Visual Studio 2013\Projects\Encryption\consoleapplication7\ClassLibrary1\Rfc2898DeriveBytes1.pas	13	1	SandWEncryptDecrypt
Error	11	(E406) No overloaded method "update" with these parameters on type "Mac", best matching overload is "method update(arg1: array of SByte; arg2: Integer; arg3: Integer)"	C:\Users\Mark\Documents\Visual Studio 2013\Projects\Encryption\consoleapplication7\ClassLibrary1\Rfc2898DeriveBytes1.pas	97	18	SandWEncryptDecrypt
Warning	12	(H3) parameter 1 is "array of nullable Byte" should be "array of SByte"	C:\Users\Mark\Documents\Visual Studio 2013\Projects\Encryption\consoleapplication7\ClassLibrary1\Rfc2898DeriveBytes1.pas	97	25	SandWEncryptDecrypt
Error	13	(E406) No overloaded method "doFinal" with these parameters on type "Mac", best matching overload is "method doFinal(arg1: array of SByte): array of SByte"	C:\Users\Mark\Documents\Visual Studio 2013\Projects\Encryption\consoleapplication7\ClassLibrary1\Rfc2898DeriveBytes1.pas	98	49	SandWEncryptDecrypt
Warning	14	(H3) parameter 1 is "array of nullable Byte" should be "array of SByte"	C:\Users\Mark\Documents\Visual Studio 2013\Projects\Encryption\consoleapplication7\ClassLibrary1\Rfc2898DeriveBytes1.pas	98	57	SandWEncryptDecrypt
Error	15	(E406) No overloaded method "doFinal" with these parameters on type "Mac", best matching overload is "method doFinal(arg1: array of SByte): array of SByte"	C:\Users\Mark\Documents\Visual Studio 2013\Projects\Encryption\consoleapplication7\ClassLibrary1\Rfc2898DeriveBytes1.pas	105	34	SandWEncryptDecrypt
Warning	16	(H3) parameter 1 is "array of nullable Byte" should be "array of SByte"	C:\Users\Mark\Documents\Visual Studio 2013\Projects\Encryption\consoleapplication7\ClassLibrary1\Rfc2898DeriveBytes1.pas	105	42	SandWEncryptDecrypt

I’m not sure how to fix the code without affecting what it does. The actual .net version seems to always return positive integers and anything I’ve done in changing array of byte to array of sbyte has given results with positive and negatives values in the getBytes method.

This was the best I could do at cleaning it up and getting past compiler errors (but of course, it doesn’t work right)

namespace SandW;

interface
uses
  java.io,
  java.security,
  javax.crypto,
  javax.crypto.spec;

   type
      // 
      // RFC 2898 password derivation compatible with .NET Rfc2898DeriveBytes class. 
      // 
      Rfc2898DeriveBytes = public class
         private
            _hmacSha1         : Mac;
            _salt             : array of SByte;
            _iterationCount   : Integer;
            _buffer           : array of SByte := new SByte[20];
            _bufferStartIndex : Integer := 0;
            _bufferEndIndex   : Integer := 0;
            _block            : Integer := 1;
         public
            constructor( password: array of SByte; salt: array of SByte; iterations: Integer );
            constructor( password: String;         salt: array of SByte; iterations: Integer );
            constructor( password: String;         salt: array of SByte );
            method getBytes(count: Integer): array of SByte;
         private
            method func(): array of SByte;
            class method getBytesFromInt(i: Integer): array of SByte;
         end;

implementation

   constructor Rfc2898DeriveBytes(password: array of SByte; salt: array of SByte; iterations: Integer);
      begin
      if ( salt = nil ) or 
         ( salt.length < 8) then 
         raise new InvalidKeyException('Salt must be 8 bytes or more.');
      if password = nil then 
         raise new InvalidKeyException('Password cannot be null.');

      _salt := salt;
      _iterationCount := iterations;
      _hmacSha1       := Mac.getInstance('HmacSHA1');
      _hmacSha1.init( new SecretKeySpec( password, 'HmacSHA1' ) )
      end;

   constructor Rfc2898DeriveBytes(password: String; salt: array of SByte; iterations: Integer);
      begin
      constructor( password.getBytes('UTF8'), salt, iterations );
      end;

   constructor Rfc2898DeriveBytes(password: String; salt: array of SByte);
      begin
      constructor( password, salt, $3e8 )
      end;

   method Rfc2898DeriveBytes.getBytes( count: Integer ) : array of SByte;
      begin
      var &result: array of SByte := new SByte[count];
      var resultOffset: Integer := 0;
      var bufferCount: Integer  := _bufferEndIndex - _bufferStartIndex;
      if bufferCount > 0 then begin
         // if there is some data in buffer
         if count < bufferCount then begin
            // if there is enough data in buffer
            System.arraycopy( _buffer, _bufferStartIndex, &result, 0, count );
            _bufferStartIndex := _bufferStartIndex + count;
            exit &result;
            end;

         System.arraycopy( _buffer, _bufferStartIndex, &result, 0, bufferCount );
         _bufferStartIndex := 0;
         _bufferEndIndex   := 0;
         resultOffset      := resultOffset + bufferCount;
         end;
      
      while resultOffset < count do begin
         var needCount: Integer := count - resultOffset;
         _buffer := func();
         if needCount > 20 then begin
            // we need one (or more) additional passes
            System.arraycopy( _buffer, 0, &result, resultOffset, 20 );
            resultOffset := resultOffset + 20
            end
         else begin
            System.arraycopy( _buffer, 0, &result, resultOffset, needCount );
            _bufferStartIndex := needCount;
            _bufferEndIndex   := 20;
            exit &result
            end
         end;
      end;

   method Rfc2898DeriveBytes.func(): array of SByte;
      begin
      _hmacSha1.update( _salt, 0, _salt.length );
      var tempHash: array of SByte := _hmacSha1.doFinal( getBytesFromInt( _block ) );
      _hmacSha1.reset();
      var finalHash: array of SByte := tempHash;
      // TODO: Check continue in unwrapped java for loop 
      var i: Integer := 2;
      while i <= _iterationCount do begin
         tempHash := _hmacSha1.doFinal( tempHash );
         // TODO: Check continue in unwrapped java for loop 
         var j: Integer := 0;
         while j < 20 do begin
            finalHash[j] := SByte((finalHash[j] xor tempHash[j]));
            {postfix}inc(j)
            end;
         {postfix}inc(i)
         end;
   
      if _block = 2147483647 
         then _block := - 2147483648
         else _block := _block + 1;

      exit finalHash
      end;

   class method Rfc2898DeriveBytes.getBytesFromInt( i: Integer ): array of SByte;
      begin
      exit array of SByte( [Byte(i shr 24), Byte(i shr 16), Byte(i shr 8), Byte(i)] )
      end;

   end.

Thanks, logged as bugs://71284

Logged a bug for the importer, it should use SByte indeed.

What doesn’t work about the fixed code?

When I have this code in .Net:

   Extension method String.Encrypt(dataToEncrypt: System.String): System.String;
      var 
         encryptor: AesManaged;
         password : String;
         salt     : array of Byte;
         rfc      : Rfc2898DeriveBytes;
         utfD1    : array of Byte;
      begin

      password := 'myPassword';
      salt     := new UTF8Encoding().GetBytes( password );
      rfc      := new Rfc2898DeriveBytes( password, salt, 1024 );

      encryptor           := new AesManaged;
      encryptor.Key       := rfc.GetBytes( 16 );
      encryptor.IV        := rfc.GetBytes( 16 );

      System.Console.WriteLine(  'key=' + Convert.ToBase64String( encryptor.Key ) );
      System.Console.WriteLine(  'iv=' + Convert.ToBase64String( encryptor.IV ) );

I get for encryptor.Key:

	[0]	44	Byte
	[1]	224	Byte
	[2]	60	Byte
	[3]	116	Byte
	[4]	138	Byte
	[5]	211	Byte
	[6]	216	Byte
	[7]	49	Byte
	[8]	126	Byte
	[9]	46	Byte
	[10]	103	Byte
	[11]	164	Byte
	[12]	63	Byte
	[13]	230	Byte
	[14]	26	Byte
	[15]	124	Byte

and for IV:

	[0]	33	Byte
	[1]	90	Byte
	[2]	201	Byte
	[3]	133	Byte
	[4]	4	Byte
	[5]	229	Byte
	[6]	11	Byte
	[7]	20	Byte
	[8]	13	Byte
	[9]	207	Byte
	[10]	131	Byte
	[11]	138	Byte
	[12]	3	Byte
	[13]	44	Byte
	[14]	97	Byte
	[15]	119	Byte

for this “java” code:

   method MyCipher.encryptString( clearText : String ) : array of SByte;
      var
         rfc      : Rfc2898DeriveBytes;
         salt     : array of SByte;
       begin

     salt     := password.getBytes( 'UTF-8' );

      rfc      := new Rfc2898DeriveBytes( password, salt, 1024 );

      var key    := rfc.getBytes( 16 );
      var iv       := rfc.getBytes( 16 );

Where password is the same, I get for key:

	[0]	-75	ShortInt
	[1]	29	ShortInt
	[2]	126	ShortInt
	[3]	-19	ShortInt
	[4]	31	ShortInt
	[5]	-37	ShortInt
	[6]	-27	ShortInt
	[7]	-117	ShortInt
	[8]	27	ShortInt
	[9]	69	ShortInt
	[10]	118	ShortInt
	[11]	84	ShortInt
	[12]	14	ShortInt
	[13]	-63	ShortInt
	[14]	-72	ShortInt
	[15]	-68	ShortInt

and for iv:

[0]	8	ShortInt
[1]	116	ShortInt
[2]	7	ShortInt
[3]	59	ShortInt
[4]	-75	ShortInt
[5]	29	ShortInt
[6]	126	ShortInt
[7]	-19	ShortInt
[8]	31	ShortInt
[9]	-37	ShortInt
[10]	-27	ShortInt
[11]	-117	ShortInt
[12]	27	ShortInt
[13]	69	ShortInt
[14]	118	ShortInt
[15]	84	ShortInt

Using this bit of code in the “java” version:

method MyCipher.createKey : SecretKeySpec;

  var
     salt     : array of SByte;
     factory  : SecretKeyFactory;
     spec     : KeySpec;
     tmp      : SecretKey;

  begin
  salt     := password.getBytes( 'UTF-8' );

  factory  := SecretKeyFactory.getInstance( 'PBKDF2WithHmacSHA1' );
  spec     := new PBEKeySpec( password.toCharArray(),  salt, 1024, 128 );
  
  tmp      := factory.generateSecret( spec );
  result   := new SecretKeySpec( tmp.getEncoded(), 'AES' );

  System.out.println("Key=" + Base64.encode( result.getEncoded() ) );
  end;

I can get the right value for key (when considering the signed numbers as unsigned), but I don’t know how to get the value for IV then.

I’m afraid I’m not familiar enough with this algorithm to answer your question. The Importer will always try to do it’s best but it’s not perfect of course.

ok, I wish I were familiar with the algorithm :slight_smile: And unfortunately, I think I can’t use Reflector to look inside the .Net one to see more.

I think I’m going to have to take a different approach.

bugs://71284 got closed with status fixed.