From 862aa7ac890f37a7537f2cadb5e1d91fce541885 Mon Sep 17 00:00:00 2001 From: Shargon Date: Fri, 21 Feb 2020 10:39:35 +0100 Subject: [PATCH] Add Salt and NEP2 to db3 wallet (#1444) * NEP2 * Clean code * Use MasterKey as passphrase * Add Salt * Unique Scrypt for all wallets * Clean using * Clean parameters * Fix UT * Random Salt --- src/neo/Wallets/SQLite/Account.cs | 2 +- src/neo/Wallets/SQLite/UserWallet.cs | 67 +++++++++++-------- .../Wallets/SQLite/VerificationContract.cs | 1 - src/neo/Wallets/SQLite/WalletDataContext.cs | 2 +- .../Wallets/SQLite/UT_Account.cs | 6 +- 5 files changed, 44 insertions(+), 34 deletions(-) diff --git a/src/neo/Wallets/SQLite/Account.cs b/src/neo/Wallets/SQLite/Account.cs index ce8ed1e01e..77aef83d93 100644 --- a/src/neo/Wallets/SQLite/Account.cs +++ b/src/neo/Wallets/SQLite/Account.cs @@ -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; } } } diff --git a/src/neo/Wallets/SQLite/UserWallet.cs b/src/neo/Wallets/SQLite/UserWallet.cs index 53d0795708..36b865a2e3 100644 --- a/src/neo/Wallets/SQLite/UserWallet.cs +++ b/src/neo/Wallets/SQLite/UserWallet.cs @@ -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; @@ -10,6 +11,7 @@ using System.Reflection; using System.Security; using System.Security.Cryptography; +using System.Text; namespace Neo.Wallets.SQLite { @@ -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 accounts; public override string Name => Path.GetFileNameWithoutExtension(path); @@ -37,38 +41,60 @@ public override Version Version } } - private UserWallet(string path, byte[] passwordKey, bool create) + /// + /// Constructor + /// + /// Path + /// Password Key + /// True for create the wallet + /// Scrypt initialization value (only if create=True) + 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(); 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) { @@ -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) @@ -193,7 +215,7 @@ public override WalletAccount CreateAccount(byte[] privateKey) Key = key, Contract = contract }; - AddAccount(account, false); + AddAccount(account); return account; } @@ -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; @@ -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) @@ -293,13 +303,14 @@ public override IEnumerable GetAccounts() { using (WalletDataContext ctx = new WalletDataContext(path)) { + string passphrase = Encoding.UTF8.GetString(masterKey); Dictionary 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(); 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; } @@ -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")); } } } diff --git a/src/neo/Wallets/SQLite/VerificationContract.cs b/src/neo/Wallets/SQLite/VerificationContract.cs index b195f50ca5..9e9016b96b 100644 --- a/src/neo/Wallets/SQLite/VerificationContract.cs +++ b/src/neo/Wallets/SQLite/VerificationContract.cs @@ -1,6 +1,5 @@ using Neo.IO; using Neo.SmartContract; -using Neo.VM; using System; using System.IO; using System.Linq; diff --git a/src/neo/Wallets/SQLite/WalletDataContext.cs b/src/neo/Wallets/SQLite/WalletDataContext.cs index 6d1ec957a8..efc22b58d9 100644 --- a/src/neo/Wallets/SQLite/WalletDataContext.cs +++ b/src/neo/Wallets/SQLite/WalletDataContext.cs @@ -32,7 +32,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) base.OnModelCreating(modelBuilder); modelBuilder.Entity().ToTable(nameof(Account)); modelBuilder.Entity().HasKey(p => p.PublicKeyHash); - modelBuilder.Entity().Property(p => p.PrivateKeyEncrypted).HasColumnType("VarBinary").HasMaxLength(96).IsRequired(); + modelBuilder.Entity().Property(p => p.Nep2key).HasColumnType("VarChar").HasMaxLength(byte.MaxValue).IsRequired(); modelBuilder.Entity().Property(p => p.PublicKeyHash).HasColumnType("Binary").HasMaxLength(20).IsRequired(); modelBuilder.Entity
().ToTable(nameof(Address)); modelBuilder.Entity
().HasKey(p => p.ScriptHash); diff --git a/tests/neo.UnitTests/Wallets/SQLite/UT_Account.cs b/tests/neo.UnitTests/Wallets/SQLite/UT_Account.cs index 0fabb80b1e..d04a31e7e1 100644 --- a/tests/neo.UnitTests/Wallets/SQLite/UT_Account.cs +++ b/tests/neo.UnitTests/Wallets/SQLite/UT_Account.cs @@ -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]