使用密码加密.INI文件字符串的简单代码

10

我正在寻找比ROT13更复杂的东西,但不需要使用库(最好甚至不需要单元,只需一个即插即用函数)。

我想对给定的字符串使用用户提供的密码进行对称加密/解密。但结果必须是一个字符串,也就是说,我必须能够将其存储在.INI文件中。

有没有人有一个简单的函数可以做到这一点(Delphi XE2)?今天谷歌不是我的朋友。

提前致谢


[更新] / [赏金] 为了明确(如果最初没有这样做,请道歉),我不想要哈希。我有一个列表框,用户可以添加/修改/删除条目。当程序关闭并重新启动时,我想将它们存储在.INI文件中。任何查看.INI文件的人(例如,在记事本中打开它)都不应该能够读取这些字符串。

我想,我可以将组件作为二进制流传输,但出于安全考虑,我宁愿使用用户提供的密码加密字符串。对于此应用程序的目的,.INI文件部分名称或键值是否可读并不重要,我只想加密数据,使其在磁盘上存储时像这样:

[config]
numEntries=3

[listbox]
0=ywevdyuvewfcyuw
1=edw
2=hr4uifareiuf

1
为什么不尝试使用MD5或SHA?你的Delphi版本是什么? - A1Gard
1
使用RC4.pas,您可以像下面的代码片段一样对流进行加密/解密。 Procedure LoadMemIniFileCrypted(INI: TMemInifile; const fn: String; Key: String); var s: String; sl: TStringList; st: TStringStream; RC4Reader: TRC4StreamReader; begin if not FileExists(fn) then Exit; sl := TStringList.Create; st := TStringStream.Create; try RC4Reader := TRC4StreamReader.Create(TFileStream.Create(fn, fmOpenRead or fmShareDenyWrite), Key); - bummi
1
  1. Ini 可以直接存储流
  2. 你对单位有什么问题?
  3. 你对库有什么问题?
- Sir Rufo
1
WriteBinaryStream将一个值作为十六进制文本写入 - 您可以使用它,但在写入之前需要对其进行加密,并在读取后解密。 - Arnaud Bouchez
4
关于需要使用字符串的要求:您可以使用任何加密方式,然后只需对其进行Base64编码即可。这就是许多加密库已经采用的方式,以便输出仍然可以是字符串。 - Jan Doggen
显示剩余6条评论
5个回答

11

免责声明

本答案使用的加密算法非常基础,任何具备中高级密码学技能的个人都可以轻松破解。之所以在解决方案中使用它,是因为原始问题要求提供一个简单的对称解决方案,而不需要使用任何库。

原理

该解决方案基于异或密码。维基百科上的定义如下:

在密码学中,简单的异或密码是一种加法密码类型,一个根据以下原则运作的加密算法:

A X 0 = A,

A X A = 0,

(A X B) X C = A X (B X C),

(B X A) X A = B X 0 = B,

其中X表示异或操作。

步骤

我提出的解决方案基于这个基本例程:

function XorCipher(const Key, Source: TBytes): TBytes;
var
  I: Integer;
begin
  if Length(Key) = 0 then
    Exit(Source);
  SetLength(Result, Length(Source));
  for I := Low(Source) to High(Source) do
    Result[I] := Key[I mod Length(Key)] xor Source[I];
end;

该程序接受一个密钥和一组字节数组作为源数据,并返回结果XOR后的字节数组。使用相同的密钥进行加密和解密,可以使用同一例程函数。要加密,源是明文数据;要解密,则源是加密数据。

我编写了两个辅助例程,以允许将结果存储为字符串。其中一个将字节数组转换为十六进制数字的文本序列,另一个则执行反向转换:

function BytesToStr(const Bytes: TBytes): string;
var
  I: Integer;
begin
  Result := '';
  for I := Low(Bytes) to High(Bytes) do
    Result := Result + LowerCase(IntToHex(Bytes[I], 2));
end;

function StrToBytes(const value: string): TBytes;
var
  I: Integer;
begin
  SetLength(Result, Length(value) div 2);
  for I := Low(Result) to High(Result) do
    Result[I] := StrToIntDef('$' + Copy(value, (I * 2) + 1, 2), 0);
end;

有了这些基础,您可以构建所需的所有内容。为了方便测试我的代码,我创建了一些其他例程,例如:

  • 将密钥存储在exe中并作为TBytes值获取的函数

function GetKey: TBytes;
begin
  Result := TArray<Byte>.Create(
     $07, $14, $47, $A0, $F4, $F7, $FF, $48, $21, $32
   , $AF, $87, $09, $8E, $B3, $C0, $7D, $54, $45, $87
   , $8A, $A8, $23, $32, $00, $56, $11, $1D, $98, $FA
  );
end;

你可以提供任意长度的密钥,因为它会滚动加密XorCipher例程中的数据。

  • 使用此方法可以使用该密钥正确编码给定字符串:

  • function XorEncodeStr(const Source: string): string; overload;
    var
      BSource: TBytes;
    begin
      SetLength(BSource, Length(Source) * SizeOf(Char));
      Move(Source[1], BSource[0], Length(Source) * SizeOf(Char));
      Result := XorEncodeToStr(GetKey, BSource);
    end;
    
  • 这个可以正确解码编码字符串为字符串的另一种方法

  • function XorDecodeStr(const Source: string): string; overload;
    var
      BResult: TBytes;
    begin
      BResult := XorDecodeFromStr(GetKey, source);
      Result := TEncoding.Unicode.GetString( BResult );
    end;
    

    编写INI文件

    使用这些程序,您可以轻松地编写和读取INI文件,例如:

    procedure TForm1.SaveIni;
    var
      Ini: TIniFile;
      I: Integer;
    begin
      Ini := TIniFile.Create('.\config.ini');
      try
        Ini.WriteInteger('config', 'NumEntries', ListBox1.Items.Count);
        for I := 0 to ListBox1.Items.Count - 1 do
          Ini.WriteString('listbox', IntToStr(I), XorEncodeStr(listbox1.Items[I]));
      finally
        Ini.Free;
      end;
    end;
    
    procedure TForm1.LoadIni;
    var
      Ini: TIniFile;
      Max, I: Integer;
    begin
      ListBox1.Items.Clear;
      Ini := TIniFile.Create('.\config.ini');
      try
        Max := Ini.ReadInteger('config', 'NumEntries', 0);
        for I := 0 to Max - 1 do
          ListBox1.Items.Add(
            XorDecodeStr(Ini.ReadString('listbox', IntToStr(I), ''))
          );
      finally
        Ini.Free;
      end;
    end;
    

    这不是生产就绪的代码,因为它只是用来测试解决方案的。但这也是您使其坚如磐石的起点。

    注意事项

    这不是强加密,因此不要依赖它来存储非常敏感的信息。一个薄弱点是密钥以明文形式包含在您的exe中。您可以处理此问题,但主要薄弱点是算法本身。

    以这个问题为例:由于您正在使用UTF-16格式对Unicode Delphi字符串进行编码,每个字符的第二个字节通常为零(除非您在东方或具有非拉丁字母表的国家),您将在编码后的存储字符串中找到确切的密钥字节重复出现。您可以通过不使用编码数据的纯十六进制表示(例如使用此处已建议的base64进行编码)来减少此现象。

    您可以使用AnsiStrings来避免揭示密钥的这些部分,或者可以使用显式零字节(或其他常量字节)对密钥进行编码。

    如果您的软件用户没有加密知识,那么任何这些方法都可以奏效。但实际上,任何具有中等水平的知识和良好技能的人都可以通过分析您的数据获取密钥。如果用户知道未经编码的值,那么就更容易了。


    @achguate +1 感谢您的辛勤付出。是的,XOR加密算法相当薄弱,所以我将依靠安全性通过混淆来保护 :-( 不用担心密码会在.EXE文件中 - 它不会;用户会在运行时被提示输入密码。再次感谢您对这个讨论做出的贡献。 - Mawg says reinstate Monica
    由于您的要求比 ROT13 更复杂,因此异或看起来是一个很好的选择,因为它比 ROT13 复杂得多。在我看来,异或不是通过模糊性提供安全性的,只是一种薄弱的方法,但在我看来它仍然可以算作是真正的密码学。 - jachguate
    1
    是我自己的问题,还是XorDecodeFromStr()函数的实现确实缺失了? - Steve Childs
    @Steve,不需要XorDecodeFromStr,因为XOR的工作方式。如果你将明文和密钥传递给XorEncodeStr,你会得到"编码"字符串。如果你将编码后的字符串和密钥传递过去,就可以再次得到明文。 - jachguate

    11

    这是Tinifile的替代品。
    ReadString和WriteString被重写了,它们用于内部的Read/WriteFloat、Read/WriteInteger等操作。

    字符串已加密,并以十六进制字符串形式存储。

    Demo用法:

    uses CryptingIni;
    {$R *.dfm}
    
    procedure TForm1.Button1Click(Sender: TObject);
    var
     ini:TCryptingIni;
    begin
        ini:=TCryptingIni.Create('C:\temp\test.ini');
        ini.UseInternalVersion(1234);
        ini.WriteFloat('Sect','Float',123.456);
        ini.WriteString('Sect2','String','How to encode');
        ini.Free;
    end;
    
    procedure TForm1.Button2Click(Sender: TObject);
    var
     ini:TCryptingIni;
    begin
        ini:=TCryptingIni.Create('C:\temp\test.ini');
        ini.UseInternalVersion(1234);
        Showmessage(FloatToStr(ini.ReadFloat('Sect','Float',0)));
        Showmessage(ini.ReadString('Sect2','String',''));
        Showmessage(ini.ReadString('SectUnkknow','Showdefault','DEFAULT'));
        ini.Free;
    end;
    

    您可以通过使用UseInternalVersion来使用内部加密方法,或者使用SetCryptingData过程来提供自己的加密过程,其中包括aEncryptProc,aDecryptProc和aKey参数。

    unit CryptingIni;
    
    // 2013 by Thomas Wassermann
    interface
    
    uses
      Windows, SysUtils, Variants, Classes, inifiles;
    
    type
    
      CryptingProc = Function(const InString: String; Key: Word): String;
    
      TCryptingIni = Class(TInifile)
        function ReadString(const Section, Ident, Default: string): string; override;
        procedure WriteString(const Section, Ident, Value: String); override;
      private
        FEncryptProc: CryptingProc;
        FDecryptProc: CryptingProc;
        FKey: Word;
      public
        Procedure SetCryptingData(aEncryptProc, aDecryptProc: CryptingProc; aKey: Word);
        Procedure UseInternalVersion(aKey: Word);
      End;
    
    implementation
    
    const
      c1 = 52845;
      c2 = 22719;
    
    Type
      TByteArray = Array [0 .. 0] of byte;
    
    Function AsHexString(p: Pointer; cnt: Integer): String;
    var
      i: Integer;
    begin
      Result := '';
      for i := 0 to cnt do
        Result := Result + '$' + IntToHex(TByteArray(p^)[i], 2);
    end;
    
    Procedure MoveHexString2Dest(Dest: Pointer; Const HS: String);
    var
      i: Integer;
    begin
      i := 1;
      while i < Length(HS) do
      begin
        TByteArray(Dest^)[i div 3] := StrToInt(Copy(HS, i, 3));
        i := i + 3;
      end;
    end;
    
    function EncryptV1(const s: string; Key: Word): string;
    var
      i: smallint;
      ResultStr: string;
      UCS: WIDEString;
    begin
      Result := s;
      if Length(s) > 0 then
      begin
        for i := 1 to (Length(s)) do
        begin
          Result[i] := Char(byte(s[i]) xor (Key shr 8));
          Key := (smallint(Result[i]) + Key) * c1 + c2
        end;
        UCS := Result;
        Result := AsHexString(@UCS[1], Length(UCS) * 2 - 1)
      end;
    end;
    
    function DecryptV1(const s: string; Key: Word): string;
    var
      i: smallint;
      sb: String;
      UCS: WIDEString;
    begin
      if Length(s) > 0 then
      begin
        SetLength(UCS, Length(s) div 3 div 2);
        MoveHexString2Dest(@UCS[1], s);
        sb := UCS;
        SetLength(Result, Length(sb));
        for i := 1 to (Length(sb)) do
        begin
          Result[i] := Char(byte(sb[i]) xor (Key shr 8));
          Key := (smallint(sb[i]) + Key) * c1 + c2
        end;
      end
      else
        Result := s;
    end;
    
    { TCryptingIni }
    
    function TCryptingIni.ReadString(const Section, Ident, Default: string): string;
    begin
      if Assigned(FEncryptProc) then
        Result := inherited ReadString(Section, Ident, FEncryptProc(Default, FKey))
      else
        Result := inherited ReadString(Section, Ident, Default);
      if Assigned(FDecryptProc) then
        Result := FDecryptProc(Result, FKey);
    end;
    
    procedure TCryptingIni.SetCryptingData(aEncryptProc, aDecryptProc: CryptingProc; aKey: Word);
    begin
      FEncryptProc := aEncryptProc;
      FDecryptProc := aDecryptProc;
      FKey := aKey;
    end;
    
    procedure TCryptingIni.UseInternalVersion(aKey: Word);
    begin
      FKey := aKey;
      FEncryptProc := EncryptV1;
      FDecryptProc := DecryptV1;
    end;
    
    procedure TCryptingIni.WriteString(const Section, Ident, Value: String);
    var
      s: String;
    begin
      if Assigned(FEncryptProc) then
        s := FEncryptProc(Value, FKey)
      else
        s := Value;
      inherited WriteString(Section, Ident, s);
    end;
    
    end.
    

    1
    +1另一个非常好的代码(算法有点弱,但我想我可以接受)。你展示了一些好的技巧,对于其他阅读这篇文章的人应该是有帮助的。谢谢。 - Mawg says reinstate Monica
    宽字符串在Android中不受支持,我已经使用了字符串,但解密没有提供正确的结果。我们应该使用哪一个? - Work 2 Enjoy - Enjoy 2 Work
    如果恶意用户可以访问.ini文件,则存在重大安全漏洞。只需要在ini文件中交换Username=和Password=字段,就可以在用户名框中显示密码。因此,请编写自己的加密和解密程序,其中Section和Ident是参数,并将其纳入这些过程中。感谢您在这里提供的出色起点 :) - Jon

    7
    我使用Delphi加密手册,它具有出色的哈希和对称加密/解密功能。它被分成单元,但不需要任何外部库,并且速度相当快。
    这是我在代码中使用它的方式:
    function Encrypt(const AStr: string): string;
    begin
      Result := AStr;
      with TCipher_Gost.Create do
        try
          Init(THash_SHA1.KDFx('Encryption Key', '', Context.KeySize));
          Result := EncodeBinary(Result, TFormat_HEX);
        finally
          Free;
        end;
    end;
    
    function Decrypt(const AStr: string): string;
    begin
      Result := AStr;
      with TCipher_Gost.Create do
        try
          Init(THash_SHA1.KDFx('Encryption Key', '', Context.KeySize));
          Result := DecodeBinary(Result, TFormat_HEX);
        finally
          Free;
        end;
    end;
    

    您可以使用任何一个 TCipher_* 类来代替 GOST。

    +1 这看起来不错,但无法编译。什么是“二进制”类型?谢谢。 - Mawg says reinstate Monica
    1
    你使用的是哪个版本的Delphi?我认为在Delphi 2009中,二进制是WideString而没有字符编码。 - iMan Biglari
    1
    DECCipher, DECHash, DECFmt - iMan Biglari
    @Mawg 如果你想让尝试破解你存储的密码的人头痛,你也可以使用循环来对数据进行多次加密。;-) - iMan Biglari
    1
    Delphi Encryption Compendium 中的 BlowFish 加密/解密例程允许加密/解密字符串,包括密码字符串。这些应该可以直接满足 OP 的要求。 - LU RD
    1
    @LURD,我也会选择blowfish->base64,因为它简单、快速、无专利,并且没有已知的弱点。 - kobik

    5
    首先,请查看此链接以获取我使用的wincrypt单元 (点击此处),因为我在这里使用了它。
    对于加密,它会将输入的字符串(由于您正在使用INI,所以所有字符串都是单个的,对吧?)通过输入的密码运行WinCrypt 3DES,然后由于它生成二进制数据,所以我将其通过Base64运行。对于解密,我则反向进行此过程。如果密码不正确,则解密后会产生垃圾数据,但根据我的测试,只要两个步骤的密码正确,它似乎能正常工作。当然,我可能忘记做一些清理工作,但如果是这种情况,它可以很容易地被修复。
    function DecryptStringW(instr, pwd: WideString): WideString;
    // password based decryption of a string using WinCrypt API, WideString version
      var
        Key: TCryptKey;
        Hash: TCryptHash;
        Prov: TCryptProv;
        DataLen, skip, Flags: DWord;
        DataBuf: Pointer;
        outstr: WideString;
      begin
        CryptAcquireContext(Prov, nil, nil, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT);
        CryptCreateHash(Prov, CALG_SHA, nil, 0, hash);
        CryptHashData(hash, @pwd[1], Length(Pwd), 0);
        CryptDeriveKey(Prov, CALG_3DES, hash, 0, key);
        CryptDestroyHash(hash);
    
        CryptStringToBinaryW(pointer(instr), Length(instr), CRYPT_STRING_BASE64, nil, DataLen, skip, Flags);
        GetMem(databuf, DataLen);
        try
          CryptStringToBinaryW(pointer(instr), Length(instr), CRYPT_STRING_BASE64, DataBuf,
               DataLen, skip, Flags);
          CryptDecrypt(Key, nil, True, 0, DataBuf, Datalen);
          SetLength(outstr, datalen);
          Move(DataBuf^, outstr[1], DataLen);
          CryptReleaseContext(Prov, 0);
          Result := outstr;
        finally
          FreeMem(databuf);
        end;
     end;
    
     function EncryptStringW(instr, pwd: WideString): WideString;
     // password based encryption of a string, WideString version
       var
        Key: TCryptKey;
        Hash: TCryptHash;
        Prov: TCryptProv;
        DataLen, bufsize: DWord;
        databuf: PByte;
        outstr: WideString;
      begin
        CryptAcquireContext(Prov, nil, nil, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT);
        CryptCreateHash(Prov, CALG_SHA, nil, 0, hash);
        CryptHashData(hash, @pwd[1], Length(Pwd), 0);
        CryptDeriveKey(Prov, CALG_3DES, hash, 0, key);
        CryptDestroyHash(hash);
        bufsize := 0;
        DataLen := 0;
        CryptEncrypt(Key, nil, True, 0, nil, bufsize, Length(instr));
        GetMem(databuf, bufsize);
        try
          Move(instr[1], databuf^, Length(instr));
          DataLen := Length(instr);
          CryptEncrypt(Key, nil, True, 0, databuf, DataLen, bufsize);
          CryptReleaseContext(Prov, 0);
          CryptBinaryToStringW(databuf, DataLen, CRYPT_STRING_BASE64 or
                  CRYPT_STRING_NOCRLF, nil, bufsize);
          SetLength(outstr, bufsize);
          CryptBinaryToStringW(databuf, DataLen, CRYPT_STRING_BASE64 or
                  CRYPT_STRING_NOCRLF, @outstr[1], bufsize);
         // result, kill the three characters after the final one the base64 returns    ($D$A$0)
         // CRYPT_STRING_NOCRLF seems to mean nothing on XP, it might on other systems
         // you will need to change to the commented line if you are on Vista, 7, or 8
          Result := Copy(outstr, 1, Length(outstr) - 3);
         // Result := Outstr;
        finally
          FreeMem(databuf);
        end;
      end;
    
      function DecryptStringA(instr, pwd: AnsiString): AnsiString;
      // password based decryption of a string using WinCrypt API, ANSI VERSION.
        var
          Key: TCryptKey;
          Hash: TCryptHash;
          Prov: TCryptProv;
          DataLen, skip, Flags: DWord;
          DataBuf: Pointer;
          outstr: AnsiString;
        begin
          CryptAcquireContext(Prov, nil, nil, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT);
          CryptCreateHash(Prov, CALG_SHA, nil, 0, hash);
          CryptHashData(hash, @pwd[1], Length(Pwd), 0);
          CryptDeriveKey(Prov, CALG_3DES, hash, 0, key);
          CryptDestroyHash(hash);
    
          CryptStringToBinaryA(pointer(instr), Length(instr), CRYPT_STRING_BASE64, nil, DataLen, skip, Flags);
          GetMem(databuf, DataLen);
          try
            CryptStringToBinaryA(pointer(instr), Length(instr), CRYPT_STRING_BASE64, DataBuf, DataLen, skip, Flags);
            CryptDecrypt(Key, nil, True, 0, DataBuf, Datalen);
            SetLength(outstr, datalen);
            Move(DataBuf^, outstr[1], DataLen);
            CryptReleaseContext(Prov, 0);
            Result := outstr;
          finally
            FreeMem(databuf);
          end;
       end;
    
      function EncryptStringA(instr, pwd: AnsiString): AnsiString;
       // password based encryption of a string, ANSI version
        var
          Key: TCryptKey;
          Hash: TCryptHash;
          Prov: TCryptProv;
          DataLen, bufsize: DWord;
          databuf: PByte;
          outstr: AnsiString;
       begin
         CryptAcquireContext(Prov, nil, nil, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT);
         CryptCreateHash(Prov, CALG_SHA, nil, 0, hash);
         CryptHashData(hash, @pwd[1], Length(Pwd), 0);
         CryptDeriveKey(Prov, CALG_3DES, hash, 0, key);
         CryptDestroyHash(hash);
         DataLen := 0;
         bufsize := 0;
         CryptEncrypt(Key, nil, True, 0, nil, bufsize, Length(instr));
         GetMem(databuf, bufsize);
         try
           Move(instr[1], databuf^, Length(instr));
           DataLen := Length(instr);
           CryptEncrypt(Key, nil, True, 0, databuf, DataLen, bufsize);
           CryptReleaseContext(Prov, 0);
           CryptBinaryToStringA(databuf, DataLen, CRYPT_STRING_BASE64 or
                  CRYPT_STRING_NOCRLF, nil, bufsize);
           SetLength(outstr, bufsize);
           CryptBinaryToStringA(databuf, DataLen, CRYPT_STRING_BASE64 or
                  CRYPT_STRING_NOCRLF, @outstr[1], bufsize);
         // result, kill the three characters after the final one the base64 returns    ($D$A$0)
         // CRYPT_STRING_NOCRLF seems to mean nothing on XP, it might on other systems
         // you will need to change to the commented line if you are on Vista, 7, or 8
          Result := Copy(outstr, 1, Length(outstr) - 3);
         // Result := Outstr;
        finally
           FreeMem(databuf);
        end;
      end;
    

    快速使用示例:

     procedure TForm1.Button1Click(Sender: TObject);
       var
         password1: AnsiString;
       begin
         password1 := 'Test1';
         Edit2.Text := EncryptStringA(Edit1.Text, password1);
       end;
    
     procedure TForm1.Button2Click(Sender: TObject);
       var
         password1: AnsiString;
       begin
         password1 := 'Test1';
         Label1.Caption := DecryptStringA(Edit2.Text, password1);
       end;
    
     procedure TForm1.Button3Click(Sender: TObject);
       var
         password1: WideString;
       begin
         password1 := 'Test1';
         Edit2.Text := EncryptStringW(Edit1.Text, password1);
       end;
    
     procedure TForm1.Button4Click(Sender: TObject);
       var
         password1: WideString;
       begin
         password1 := 'Test1';
         Label1.Caption := DecryptStringW(Edit2.Text, password1);
       end;
    

    希望对某些人有所帮助。
    使用“Edit1”作为输入。加密ANSI的正确输出:3+Pp7o8aErc=,加密WideString的正确输出:HijzDYgRr/Y=
    编辑:我也发布了WideString版本。我下载了XE3演示版来查看和玩耍。该代码在那里以及Turbo Delphi 2006和Delphi 3上都可以工作,因此如果您遇到困难,请检查我关于Windows XP Base64实现不遵守CRYPT_STRING_NOCRLF的注释行,因为如果您在具有此功能的Windows上,则需要更改该行才能使其正常工作。无论如何,对于OP的声明意图,我们都不希望$13$10出现在编码文本中。

    1
    我猜这是 Ansi 版本?我已在 D7/Win7 中进行测试。在将“DataLen,skip,Flags”更改为“DWORD”之前,它无法编译。对 Edit1 进行加密的结果是“3+Pp7o8aEr”,但解密后的字符串显示了垃圾字符。当我不“杀死最后一个字符后面的三个字符”时,它可以正常工作。 - kobik
    1. 是的,这是ANSI版本。我认为只需要明确定义AnsiStrings而不是通用字符串即可澄清一切。从那里开始,制定WideString版本应该就足够简单了。
    2. 在查看CryptBinaryToString函数时,CRYPT_STRING_NOCRLF常量在Windows XP(我测试过的地方)没有意义。因此,我不得不删除CR/LF对(根据评论)以获得我要查找的内容 - 在Windows 7上,该行似乎不必要。
    - Glenn1234
    1
    @Mawg,我在帖子的第一句话中链接了我使用的单元的正确版本。除此之外,您可能需要明确定义要作为ANSIStrings的字符串。同时,将您指定的类型更改为DWord也是不错的选择,还要检查最后一行,如评论所示。我使用较旧版本的Delphi,并且也在Windows XP上,因此可能需要一些工作。但我可以说的是,它在这里运行得很好,只是要删除CryptoAPI坚持放入base64字符串中的$D和$A。 - Glenn1234
    1
    @Mawg 我能建议的就是,如果你不在XP上,请将Result:= Copy(行)更改为Result:= outstr; 因为CRYPT_STRING_NOCRLF在Windows Vista、7和8上运行正常。 如果它在TD 2006上运行正常,我不认为它在更新的东西上不会工作。 要说得更多,我必须看看你正在尝试什么。 - Glenn1234
    ++ 为了宽字符串国际化支持,需要对所有的 length(str) 加上 *SizeOf(WideChar)。 - Gu.
    显示剩余3条评论

    1
    base64 是非常好的编码器,具有字符串结果和标准化:
    {**************************************************************}
    {                  Base 64 - by David Barton                   }
    {--------------------------------------------------------------}
    
     const
      B64: array[0..63] of byte= (65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,
        81,82,83,84,85,86,87,88,89,90,97,98,99,100,101,102,103,104,105,106,107,108,
        109,110,111,112,113,114,115,116,117,118,119,120,121,122,48,49,50,51,52,53,
        54,55,56,57,43,47);
    
    function B64Encode(pInput: pointer; pOutput: pointer; Size: longint): longint;
    var
      i, iptr, optr: integer;
      Input, Output: PByteArray;
    begin
      Input:= PByteArray(pInput); Output:= PByteArray(pOutput);
      iptr:= 0; optr:= 0;
      for i:= 1 to (Size div 3) do
      begin
        Output^[optr+0]:= B64[Input^[iptr] shr 2];
        Output^[optr+1]:= B64[((Input^[iptr] and 3) shl 4) + (Input^[iptr+1] shr 4)];
        Output^[optr+2]:= B64[((Input^[iptr+1] and 15) shl 2) + (Input^[iptr+2] shr 6)];
        Output^[optr+3]:= B64[Input^[iptr+2] and 63];
        Inc(optr,4); Inc(iptr,3);
      end;
      case (Size mod 3) of
        1: begin
             Output^[optr+0]:= B64[Input^[iptr] shr 2];
             Output^[optr+1]:= B64[(Input^[iptr] and 3) shl 4];
             Output^[optr+2]:= byte('=');
             Output^[optr+3]:= byte('=');
           end;
        2: begin
             Output^[optr+0]:= B64[Input^[iptr] shr 2];
             Output^[optr+1]:= B64[((Input^[iptr] and 3) shl 4) + (Input^[iptr+1] shr 4)];
             Output^[optr+2]:= B64[(Input^[iptr+1] and 15) shl 2];
             Output^[optr+3]:= byte('=');
           end;
      end;
      Result:= ((Size+2) div 3) * 4;
    end;
    
    
    function Base64Encode(const Value: AnsiString): AnsiString;
    begin
      SetLength(Result,((Length(Value)+2) div 3) * 4);
      B64Encode(@Value[1],@Result[1],Length(Value));
    end;
    
    
    function B64Decode(pInput: pointer; pOutput: pointer; Size: longint): longint;
    var
      i, j, iptr, optr: integer;
      Temp: array[0..3] of byte;
      Input, Output: PByteArray;
    begin
      Input:= PByteArray(pInput); Output:= PByteArray(pOutput);
      iptr:= 0; optr:= 0;
      Result:= 0;
      for i:= 1 to (Size div 4) do
      begin
        for j:= 0 to 3 do
        begin
          case Input^[iptr] of
            65..90 : Temp[j]:= Input^[iptr] - Ord('A');
            97..122: Temp[j]:= Input^[iptr] - Ord('a') + 26;
            48..57 : Temp[j]:= Input^[iptr] - Ord('0') + 52;
            43     : Temp[j]:= 62;
            47     : Temp[j]:= 63;
            61     : Temp[j]:= $FF;
          end;
          Inc(iptr);
        end;
        Output^[optr]:= (Temp[0] shl 2) or (Temp[1] shr 4);
        Result:= optr+1;
        if (Temp[2]<> $FF) and (Temp[3]= $FF) then
        begin
          Output^[optr+1]:= (Temp[1] shl 4) or (Temp[2] shr 2);
          Result:= optr+2;
          Inc(optr)
        end
        else if (Temp[2]<> $FF) then
        begin
          Output^[optr+1]:= (Temp[1] shl 4) or (Temp[2] shr 2);
          Output^[optr+2]:= (Temp[2] shl 6) or  Temp[3];
          Result:= optr+3;
          Inc(optr,2);
        end;
        Inc(optr);
      end;
    end;
    
    function Base64Decode(const Value: AnsiString): AnsiString;
    begin
      SetLength(Result,(Length(Value) div 4) * 3);
      SetLength(Result,B64Decode(@Value[1],@Result[1],Length(Value)));
    end;
    

    你可以使用以下示例:
    编码:
    procedure TForm1.btn1Click(Sender: TObject);
    begin
      edt1.Text := Base64Encode(edt1.Text)  ;
    end;
    

    解码:

    procedure TForm1.btn1Click(Sender: TObject);
    begin
      edt1.Text := Base64Decode(edt1.Text)  ;
    end;
    

    2
    TCustomIniFile.WriteBinaryStream可以完成这项工作;o) - Sir Rufo
    2
    这并不是加密数据,而是将其转换为base64字符。 - iMan Biglari
    1
    @iManBiglari:我知道,但那是标准的方式,密码应该是哈希而不是加密或编码。 - A1Gard
    1
    OP正在寻求加密/解密,而不是哈希 - 对我来说,他需要存储密码,例如数据库连接,而散列密钥在这种情况下没有帮助。 - Sir Rufo
    5
    Delphi自带多种base64实现,不需要再添加新的。 - David Heffernan
    10
    仅回答问题的一部分:获取文件内容作为字符串。它不使用密码,并使用编码而非加密。因此不是一个有效的答案。 - Arnaud Bouchez

    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接