Created
December 22, 2020 08:24
-
-
Save RileyGe/55f588e0f29faf5465204f12bd450a0a to your computer and use it in GitHub Desktop.
Algo-Wallet密钥文件本地存储流程演示--只为流程演示,不保证可以运行
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Text; | |
using System.Collections.Generic; | |
using Org.BouncyCastle.Security; | |
using Org.BouncyCastle.Crypto; | |
using Org.BouncyCastle.Crypto.Generators; | |
using Org.BouncyCastle.Crypto.Engines; | |
using Org.BouncyCastle.Crypto.Modes; | |
using Org.BouncyCastle.Crypto.Parameters; | |
namespace privatkey_scrypt | |
{ | |
public class CryptoUtils | |
{ | |
private const int KEY_BIT_SIZE = 256; | |
private const int MAC_BIT_SIZE = 128; | |
public static byte[] DecryptAesGcm(byte[] key, byte[] nonce, byte[] cipherText, byte[] tag) | |
{ | |
List<byte> msgList = new List<byte>(cipherText); | |
msgList.AddRange(tag); | |
byte[] message = msgList.ToArray(); | |
if (key == null || key.Length != KEY_BIT_SIZE / 8) | |
throw new ArgumentException(String.Format("Key needs to be {0} bit!", KEY_BIT_SIZE), "key"); | |
if (message == null || message.Length == 0) | |
throw new ArgumentException("Message required!", "message"); | |
var cipher = new GcmBlockCipher(new AesEngine()); | |
var parameters = new AeadParameters(new KeyParameter(key), MAC_BIT_SIZE, nonce); | |
cipher.Init(false, parameters); | |
var plainText = new byte[cipher.GetOutputSize(message.Length)]; | |
try | |
{ | |
var len = cipher.ProcessBytes(message, 0, message.Length, plainText, 0); | |
cipher.DoFinal(plainText, len); | |
} | |
catch (InvalidCipherTextException) | |
{ | |
return null; | |
} | |
return plainText; | |
} | |
public static byte[] EncryptAesGcm(byte[] key, byte[] nonce, byte[] plaintext) | |
{ | |
if (key == null || key.Length != KEY_BIT_SIZE / 8) | |
throw new ArgumentException(String.Format("Key needs to be {0} bit!", KEY_BIT_SIZE), "key"); | |
var cipher = new GcmBlockCipher(new AesEngine()); | |
var parameters = new AeadParameters(new KeyParameter(key), MAC_BIT_SIZE, nonce); | |
cipher.Init(true, parameters); | |
var cipherText = new byte[cipher.GetOutputSize(plaintext.Length)]; | |
var len = cipher.ProcessBytes(plaintext, 0, plaintext.Length, cipherText, 0); | |
cipher.DoFinal(cipherText, len); | |
return cipherText; | |
} | |
public static byte[] GetCipherTextFromAesGcmResult(byte[] result) | |
{ | |
if (result.Length == 16 + 32) | |
return result.AsSpan().Slice(0, 32).ToArray(); | |
else | |
return null; | |
} | |
public static byte[] GetTagFromAesGcmResult(byte[] result) | |
{ | |
if (result.Length == 16 + 32) | |
return result.AsSpan().Slice(32, 16).ToArray(); | |
else | |
return null; | |
} | |
/// <summary> | |
/// Generate a random 16 bits salt | |
/// </summary> | |
/// <returns>16 bits salt</returns> | |
public static byte[] GenerateRandomSalt() | |
{ | |
var salt = new byte[16]; | |
new SecureRandom().NextBytes(salt); | |
return salt; | |
} | |
/// <summary> | |
/// Use Scrypt to generate 48 bytes hash. | |
/// Scrypt is used instead of Bscrypt because Bscrypt cannot work when importing the wallet | |
/// </summary> | |
/// <param name="salt">salt</param> | |
/// <param name="pwd">password</param> | |
/// <returns>the generated 48 bytes hash</returns> | |
public static byte[] GenerateHash(byte[] salt, string pwd) | |
{ | |
if (salt.Length != 16) | |
return null; | |
var password = Encoding.UTF8.GetBytes(pwd); | |
const int SCryptN = 262144; | |
return SCrypt.Generate(password, salt, SCryptN, 8, 1, 48); | |
} | |
/// <summary> | |
/// Use the first 16 bytes to check the entered password is correct | |
/// </summary> | |
/// <param name="key">a 48 bytes, usually generated by Scrypt</param> | |
/// <returns>the first 16 bytes</returns> | |
public static byte[] GetCheckSalt(byte[] key) | |
{ | |
if (key.Length != 48) | |
return null; | |
var key_list = new List<byte>(key); | |
key_list.RemoveRange(16, 32); | |
return key_list.ToArray(); | |
} | |
/// <summary> | |
/// Use the last 32 bytes as the Master-Key of algorand account | |
/// </summary> | |
/// <param name="key">a 48 bytes, usually generated by Scrypt</param> | |
/// <returns>the last 32 bytes</returns> | |
public static byte[] GetMasterKey(byte[] key) | |
{ | |
if (key.Length != 48) | |
return null; | |
var key_list = new List<byte>(key); | |
key_list.RemoveRange(0, 16); | |
return key_list.ToArray(); | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Linq; | |
using System.Text; | |
namespace privatkey_scrypt | |
{ | |
class Program | |
{ | |
static void Main(string[] args) | |
{ | |
Console.WriteLine("Hello World!"); | |
} | |
/// <summary> | |
/// 加密过程 | |
/// </summary> | |
/// <param name="accountPassword">密码</param> | |
/// <param name="masterKey">私钥:32位</param> | |
static void scrypt(string accountPassword, byte[] masterKey) | |
{ | |
var salt = CryptoUtils.GenerateRandomSalt(); //生成16位的随机salt,需要存储于本地 | |
var scryptedKey = CryptoUtils.GenerateHash(salt, accountPassword); //生成48位二进制 | |
var checkSalt = CryptoUtils.GetCheckSalt(scryptedKey); //前16位用于存储本地,后续用于较验密码 | |
//GetMasterKey只是获取了48位二进制的后32位,此32位并不是MasterKey,是用于AESGCM加密的Key | |
//由于历史原因起名为GetMasterKey,为了与Algo-Wallet一致此处也用此名字 | |
var aesgcmKey = CryptoUtils.GetMasterKey(scryptedKey); | |
//如果需要秘钥文件可以在不同的程序间迁移,则用固定值 | |
//如果需要秘钥文件在不同的程序间不可迁移,请使用 | |
//Org.BouncyCastle.Security.SecureRandom类生成随机nonce | |
var nonce = Encoding.UTF8.GetBytes("algo--wallet"); | |
// aesgcmCipherBytes为48位二进制,存储本地 | |
var aesgcmCipherBytes = CryptoUtils.EncryptAesGcm(aesgcmKey, nonce, masterKey); | |
} | |
/// <summary> | |
/// 解密过程 | |
/// </summary> | |
/// <param name="accountPassword">密码</param> | |
/// <param name="salt">存储于本地的16位salt</param> | |
/// <param name="checkSalt">存储于本地的16位二进制</param> | |
/// <param name="aesgcmCipherBytes">存储于本地的48位二进制</param> | |
static void descrypt(string accountPassword, byte[] salt, byte[] checkSalt, byte[] aesgcmCipherBytes) | |
{ | |
var scryptedKey = CryptoUtils.GenerateHash(salt, accountPassword); //生成48位二进制 | |
var calcedCheckSalt = CryptoUtils.GetCheckSalt(scryptedKey); //前16位用于存储本地,后续用于较验密码 | |
if(!Enumerable.SequenceEqual(checkSalt, calcedCheckSalt)) return; //密码错误 | |
var nonce = Encoding.UTF8.GetBytes("algo--wallet"); | |
//解密得到masterKey | |
var masterKey = CryptoUtils.DecryptAesGcm(CryptoUtils.GetMasterKey(scryptedKey), nonce, | |
CryptoUtils.GetCipherTextFromAesGcmResult(aesgcmCipherBytes), | |
CryptoUtils.GetTagFromAesGcmResult(aesgcmCipherBytes)); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment