Skip to content

Commit

Permalink
Add Salt and NEP2 to db3 wallet (neo-project#1444)
Browse files Browse the repository at this point in the history
* NEP2

* Clean code

* Use MasterKey as passphrase

* Add Salt

* Unique Scrypt for all wallets

* Clean using

* Clean parameters

* Fix UT

* Random Salt
  • Loading branch information
shargon authored and Tommo-L committed Jun 22, 2020
1 parent 6a5569a commit 862aa7a
Show file tree
Hide file tree
Showing 5 changed files with 44 additions and 34 deletions.
2 changes: 1 addition & 1 deletion src/neo/Wallets/SQLite/Account.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ namespace Neo.Wallets.SQLite
{
internal class Account
{
public byte[] PrivateKeyEncrypted { get; set; }
public byte[] PublicKeyHash { get; set; }
public string Nep2key { get; set; }
}
}
67 changes: 39 additions & 28 deletions src/neo/Wallets/SQLite/UserWallet.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using Neo.Cryptography;
using Neo.IO;
using Neo.SmartContract;
using Neo.Wallets.NEP6;
using System;
using System.Buffers.Binary;
using System.Collections.Generic;
Expand All @@ -10,6 +11,7 @@
using System.Reflection;
using System.Security;
using System.Security.Cryptography;
using System.Text;

namespace Neo.Wallets.SQLite
{
Expand All @@ -18,7 +20,9 @@ public class UserWallet : Wallet
private readonly object db_lock = new object();
private readonly string path;
private readonly byte[] iv;
private readonly byte[] salt;
private readonly byte[] masterKey;
private readonly ScryptParameters scrypt;
private readonly Dictionary<UInt160, UserWalletAccount> accounts;

public override string Name => Path.GetFileNameWithoutExtension(path);
Expand All @@ -37,38 +41,60 @@ public override Version Version
}
}

private UserWallet(string path, byte[] passwordKey, bool create)
/// <summary>
/// Constructor
/// </summary>
/// <param name="path">Path</param>
/// <param name="passwordKey">Password Key</param>
/// <param name="create">True for create the wallet</param>
/// <param name="scrypt">Scrypt initialization value (only if create=True)</param>
private UserWallet(string path, byte[] passwordKey, bool create, ScryptParameters scrypt = null)
{
this.path = path;

if (create)
{
this.iv = new byte[16];
this.salt = new byte[20];
this.masterKey = new byte[32];
this.scrypt = scrypt ?? ScryptParameters.Default;
this.accounts = new Dictionary<UInt160, UserWalletAccount>();
using (RandomNumberGenerator rng = RandomNumberGenerator.Create())
{
rng.GetBytes(iv);
rng.GetBytes(salt);
rng.GetBytes(masterKey);
}
Version version = Assembly.GetExecutingAssembly().GetName().Version;
BuildDatabase();
SaveStoredData("PasswordHash", passwordKey.Sha256());
SaveStoredData("IV", iv);
SaveStoredData("Salt", salt);
SaveStoredData("PasswordHash", passwordKey.Concat(salt).ToArray().Sha256());
SaveStoredData("MasterKey", masterKey.AesEncrypt(passwordKey, iv));
SaveStoredData("Version", new[] { version.Major, version.Minor, version.Build, version.Revision }.Select(p => BitConverter.GetBytes(p)).SelectMany(p => p).ToArray());
SaveStoredData("ScryptN", BitConverter.GetBytes(this.scrypt.N));
SaveStoredData("ScryptR", BitConverter.GetBytes(this.scrypt.R));
SaveStoredData("ScryptP", BitConverter.GetBytes(this.scrypt.P));
}
else
{
this.salt = LoadStoredData("Salt");
byte[] passwordHash = LoadStoredData("PasswordHash");
if (passwordHash != null && !passwordHash.SequenceEqual(passwordKey.Sha256()))
if (passwordHash != null && !passwordHash.SequenceEqual(passwordKey.Concat(salt).ToArray().Sha256()))
throw new CryptographicException();
this.iv = LoadStoredData("IV");
this.masterKey = LoadStoredData("MasterKey").AesDecrypt(passwordKey, iv);
this.scrypt = new ScryptParameters
(
BitConverter.ToInt32(LoadStoredData("ScryptN")),
BitConverter.ToInt32(LoadStoredData("ScryptR")),
BitConverter.ToInt32(LoadStoredData("ScryptP"))
);
this.accounts = LoadAccounts();
}
}

private void AddAccount(UserWalletAccount account, bool is_import)
private void AddAccount(UserWalletAccount account)
{
lock (accounts)
{
Expand All @@ -86,23 +112,19 @@ private void AddAccount(UserWalletAccount account, bool is_import)
{
if (account.HasKey)
{
byte[] decryptedPrivateKey = new byte[96];
Buffer.BlockCopy(account.Key.PublicKey.EncodePoint(false), 1, decryptedPrivateKey, 0, 64);
Buffer.BlockCopy(account.Key.PrivateKey, 0, decryptedPrivateKey, 64, 32);
byte[] encryptedPrivateKey = EncryptPrivateKey(decryptedPrivateKey);
Array.Clear(decryptedPrivateKey, 0, decryptedPrivateKey.Length);
string passphrase = Encoding.UTF8.GetString(masterKey);
Account db_account = ctx.Accounts.FirstOrDefault(p => p.PublicKeyHash == account.Key.PublicKeyHash.ToArray());
if (db_account == null)
{
db_account = ctx.Accounts.Add(new Account
{
PrivateKeyEncrypted = encryptedPrivateKey,
Nep2key = account.Key.Export(passphrase, scrypt.N, scrypt.R, scrypt.P),
PublicKeyHash = account.Key.PublicKeyHash.ToArray()
}).Entity;
}
else
{
db_account.PrivateKeyEncrypted = encryptedPrivateKey;
db_account.Nep2key = account.Key.Export(passphrase, scrypt.N, scrypt.R, scrypt.P);
}
}
if (account.Contract != null)
Expand Down Expand Up @@ -193,7 +215,7 @@ public override WalletAccount CreateAccount(byte[] privateKey)
Key = key,
Contract = contract
};
AddAccount(account, false);
AddAccount(account);
return account;
}

Expand All @@ -213,24 +235,17 @@ public override WalletAccount CreateAccount(SmartContract.Contract contract, Key
Key = key,
Contract = verification_contract
};
AddAccount(account, false);
AddAccount(account);
return account;
}

public override WalletAccount CreateAccount(UInt160 scriptHash)
{
UserWalletAccount account = new UserWalletAccount(scriptHash);
AddAccount(account, true);
AddAccount(account);
return account;
}

private byte[] DecryptPrivateKey(byte[] encryptedPrivateKey)
{
if (encryptedPrivateKey == null) throw new ArgumentNullException(nameof(encryptedPrivateKey));
if (encryptedPrivateKey.Length != 96) throw new ArgumentException();
return encryptedPrivateKey.AesDecrypt(masterKey, iv);
}

public override bool DeleteAccount(UInt160 scriptHash)
{
UserWalletAccount account;
Expand Down Expand Up @@ -266,11 +281,6 @@ public override bool DeleteAccount(UInt160 scriptHash)
return false;
}

private byte[] EncryptPrivateKey(byte[] decryptedPrivateKey)
{
return decryptedPrivateKey.AesEncrypt(masterKey, iv);
}

public override WalletAccount GetAccount(UInt160 scriptHash)
{
lock (accounts)
Expand All @@ -293,13 +303,14 @@ public override IEnumerable<WalletAccount> GetAccounts()
{
using (WalletDataContext ctx = new WalletDataContext(path))
{
string passphrase = Encoding.UTF8.GetString(masterKey);
Dictionary<UInt160, UserWalletAccount> accounts = ctx.Addresses.Select(p => p.ScriptHash).AsEnumerable().Select(p => new UserWalletAccount(new UInt160(p))).ToDictionary(p => p.ScriptHash);
foreach (Contract db_contract in ctx.Contracts.Include(p => p.Account))
{
VerificationContract contract = db_contract.RawData.AsSerializable<VerificationContract>();
UserWalletAccount account = accounts[contract.ScriptHash];
account.Contract = contract;
account.Key = new KeyPair(DecryptPrivateKey(db_contract.Account.PrivateKeyEncrypted));
account.Key = new KeyPair(GetPrivateKeyFromNEP2(db_contract.Account.Nep2key, passphrase, scrypt.N, scrypt.R, scrypt.P));
}
return accounts;
}
Expand Down Expand Up @@ -352,7 +363,7 @@ private static void SaveStoredData(WalletDataContext ctx, string name, byte[] va

public override bool VerifyPassword(string password)
{
return password.ToAesKey().Sha256().SequenceEqual(LoadStoredData("PasswordHash"));
return password.ToAesKey().Concat(salt).ToArray().Sha256().SequenceEqual(LoadStoredData("PasswordHash"));
}
}
}
1 change: 0 additions & 1 deletion src/neo/Wallets/SQLite/VerificationContract.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
using Neo.IO;
using Neo.SmartContract;
using Neo.VM;
using System;
using System.IO;
using System.Linq;
Expand Down
2 changes: 1 addition & 1 deletion src/neo/Wallets/SQLite/WalletDataContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder)
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Account>().ToTable(nameof(Account));
modelBuilder.Entity<Account>().HasKey(p => p.PublicKeyHash);
modelBuilder.Entity<Account>().Property(p => p.PrivateKeyEncrypted).HasColumnType("VarBinary").HasMaxLength(96).IsRequired();
modelBuilder.Entity<Account>().Property(p => p.Nep2key).HasColumnType("VarChar").HasMaxLength(byte.MaxValue).IsRequired();
modelBuilder.Entity<Account>().Property(p => p.PublicKeyHash).HasColumnType("Binary").HasMaxLength(20).IsRequired();
modelBuilder.Entity<Address>().ToTable(nameof(Address));
modelBuilder.Entity<Address>().HasKey(p => p.ScriptHash);
Expand Down
6 changes: 3 additions & 3 deletions tests/neo.UnitTests/Wallets/SQLite/UT_Account.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,13 @@ public void TestGenerator()
}

[TestMethod]
public void TestSetAndGetPrivateKeyEncrypted()
public void TestSetAndGetNep2key()
{
Account account = new Account
{
PrivateKeyEncrypted = new byte[] { 0x01 }
Nep2key = "123"
};
Assert.AreEqual(Encoding.Default.GetString(new byte[] { 0x01 }), Encoding.Default.GetString(account.PrivateKeyEncrypted));
Assert.AreEqual("123", account.Nep2key);
}

[TestMethod]
Expand Down

0 comments on commit 862aa7a

Please sign in to comment.