From 6a4863ed3a87f556ba23c38b337ff70f23c8391a Mon Sep 17 00:00:00 2001 From: Shargon Date: Tue, 11 Feb 2020 15:06:35 +0100 Subject: [PATCH 1/9] NEP2 --- src/neo/Wallets/SQLite/Account.cs | 8 +- src/neo/Wallets/SQLite/UserWallet.cs | 73 +++++++++++++------ .../Wallets/SQLite/VerificationContract.cs | 1 - src/neo/Wallets/SQLite/WalletDataContext.cs | 5 +- .../Wallets/SQLite/UT_Account.cs | 6 +- 5 files changed, 63 insertions(+), 30 deletions(-) diff --git a/src/neo/Wallets/SQLite/Account.cs b/src/neo/Wallets/SQLite/Account.cs index ce8ed1e01e..9cf3e3d540 100644 --- a/src/neo/Wallets/SQLite/Account.cs +++ b/src/neo/Wallets/SQLite/Account.cs @@ -1,8 +1,14 @@ +using Neo.Wallets.NEP6; + namespace Neo.Wallets.SQLite { internal class Account { - public byte[] PrivateKeyEncrypted { get; set; } public byte[] PublicKeyHash { get; set; } + public string Nep2key { get; set; } + + public int ScryptN { get; set; } = ScryptParameters.Default.N; + public int ScryptR { get; set; } = ScryptParameters.Default.R; + public int ScryptP { get; set; } = ScryptParameters.Default.P; } } diff --git a/src/neo/Wallets/SQLite/UserWallet.cs b/src/neo/Wallets/SQLite/UserWallet.cs index 53d0795708..2b22dd1f96 100644 --- a/src/neo/Wallets/SQLite/UserWallet.cs +++ b/src/neo/Wallets/SQLite/UserWallet.cs @@ -2,8 +2,10 @@ using Neo.Cryptography; using Neo.IO; using Neo.SmartContract; +using Neo.Wallets.NEP6; using System; using System.Buffers.Binary; +using System.Collections; using System.Collections.Generic; using System.IO; using System.Linq; @@ -19,6 +21,7 @@ public class UserWallet : Wallet private readonly string path; private readonly byte[] iv; private readonly byte[] masterKey; + private string passphrase; private readonly Dictionary accounts; public override string Name => Path.GetFileNameWithoutExtension(path); @@ -40,6 +43,7 @@ public override Version Version private UserWallet(string path, byte[] passwordKey, bool create) { this.path = path; + if (create) { this.iv = new byte[16]; @@ -50,6 +54,7 @@ private UserWallet(string path, byte[] passwordKey, bool create) rng.GetBytes(iv); rng.GetBytes(masterKey); } + this.passphrase = CreatePassphrase(passwordKey, iv); Version version = Assembly.GetExecutingAssembly().GetName().Version; BuildDatabase(); SaveStoredData("PasswordHash", passwordKey.Sha256()); @@ -63,13 +68,28 @@ private UserWallet(string path, byte[] passwordKey, bool create) if (passwordHash != null && !passwordHash.SequenceEqual(passwordKey.Sha256())) throw new CryptographicException(); this.iv = LoadStoredData("IV"); + this.passphrase = CreatePassphrase(passwordKey, iv); this.masterKey = LoadStoredData("MasterKey").AesDecrypt(passwordKey, iv); this.accounts = LoadAccounts(); } } - private void AddAccount(UserWalletAccount account, bool is_import) + private string CreatePassphrase(byte[] passwordKey, byte[] key) { + byte[] xor = new byte[passwordKey.Length]; + + for (int i = 0; i < passwordKey.Length; i++) + { + xor[i] = (byte)(passwordKey[i] ^ key[i % key.Length]); + } + + return xor.ToHexString(); + } + + private void AddAccount(UserWalletAccount account, ScryptParameters scrypt) + { + if (scrypt == null) scrypt = ScryptParameters.Default; + lock (accounts) { if (accounts.TryGetValue(account.ScriptHash, out UserWalletAccount account_old)) @@ -86,23 +106,24 @@ 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); 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, + ScryptN = scrypt.N, + ScryptR = scrypt.R, + ScryptP = scrypt.P, + Nep2key = account.Key.Export(passphrase, scrypt.N, scrypt.R, scrypt.P), PublicKeyHash = account.Key.PublicKeyHash.ToArray() }).Entity; } else { - db_account.PrivateKeyEncrypted = encryptedPrivateKey; + db_account.ScryptN = scrypt.N; + db_account.ScryptR = scrypt.R; + db_account.ScryptP = scrypt.P; + db_account.Nep2key = account.Key.Export(passphrase, scrypt.N, scrypt.R, scrypt.P); } } if (account.Contract != null) @@ -152,8 +173,23 @@ public bool ChangePassword(string password_old, string password_new) byte[] passwordKey = password_new.ToAesKey(); try { + var newpassphrase = CreatePassphrase(passwordKey, LoadStoredData("IV")); + + lock (db_lock) + using (WalletDataContext ctx = new WalletDataContext(path)) + { + foreach (var account in ctx.Accounts) + { + var key = new KeyPair(GetPrivateKeyFromNEP2(account.Nep2key, passphrase, account.ScryptN, account.ScryptR, account.ScryptP)); + account.Nep2key = key.Export(newpassphrase, account.ScryptN, account.ScryptR, account.ScryptP); + } + ctx.SaveChanges(); + } + + passphrase = newpassphrase; SaveStoredData("PasswordHash", passwordKey.Sha256()); SaveStoredData("MasterKey", masterKey.AesEncrypt(passwordKey, iv)); + return true; } finally @@ -193,7 +229,7 @@ public override WalletAccount CreateAccount(byte[] privateKey) Key = key, Contract = contract }; - AddAccount(account, false); + AddAccount(account, ScryptParameters.Default); return account; } @@ -213,24 +249,17 @@ public override WalletAccount CreateAccount(SmartContract.Contract contract, Key Key = key, Contract = verification_contract }; - AddAccount(account, false); + AddAccount(account, ScryptParameters.Default); return account; } public override WalletAccount CreateAccount(UInt160 scriptHash) { UserWalletAccount account = new UserWalletAccount(scriptHash); - AddAccount(account, true); + AddAccount(account, ScryptParameters.Default); 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 +295,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) @@ -299,7 +323,8 @@ private Dictionary LoadAccounts() 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, + db_contract.Account.ScryptN, db_contract.Account.ScryptR, db_contract.Account.ScryptP)); } return accounts; } 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..5a92df8957 100644 --- a/src/neo/Wallets/SQLite/WalletDataContext.cs +++ b/src/neo/Wallets/SQLite/WalletDataContext.cs @@ -32,7 +32,10 @@ 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(96).IsRequired(); + modelBuilder.Entity().Property(p => p.ScryptN).HasColumnType("Integer").IsRequired(); + modelBuilder.Entity().Property(p => p.ScryptR).HasColumnType("Integer").IsRequired(); + modelBuilder.Entity().Property(p => p.ScryptP).HasColumnType("Integer").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] From cd35d941e6f5a5c8bb5d7c37a18c341b646afaa4 Mon Sep 17 00:00:00 2001 From: Shargon Date: Tue, 11 Feb 2020 15:10:08 +0100 Subject: [PATCH 2/9] Clean code --- src/neo/Wallets/SQLite/UserWallet.cs | 1 - src/neo/Wallets/SQLite/WalletDataContext.cs | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/neo/Wallets/SQLite/UserWallet.cs b/src/neo/Wallets/SQLite/UserWallet.cs index 2b22dd1f96..f5b946b010 100644 --- a/src/neo/Wallets/SQLite/UserWallet.cs +++ b/src/neo/Wallets/SQLite/UserWallet.cs @@ -5,7 +5,6 @@ using Neo.Wallets.NEP6; using System; using System.Buffers.Binary; -using System.Collections; using System.Collections.Generic; using System.IO; using System.Linq; diff --git a/src/neo/Wallets/SQLite/WalletDataContext.cs b/src/neo/Wallets/SQLite/WalletDataContext.cs index 5a92df8957..2203602e52 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.Nep2key).HasColumnType("VarChar").HasMaxLength(96).IsRequired(); + modelBuilder.Entity().Property(p => p.Nep2key).HasColumnType("VarChar").HasMaxLength(byte.MaxValue).IsRequired(); modelBuilder.Entity().Property(p => p.ScryptN).HasColumnType("Integer").IsRequired(); modelBuilder.Entity().Property(p => p.ScryptR).HasColumnType("Integer").IsRequired(); modelBuilder.Entity().Property(p => p.ScryptP).HasColumnType("Integer").IsRequired(); From 39f0b5f715a19b1a9c27c43d5b32b433f6a1c3e6 Mon Sep 17 00:00:00 2001 From: Shargon Date: Fri, 14 Feb 2020 14:17:41 +0100 Subject: [PATCH 3/9] Use MasterKey as passphrase --- src/neo/Wallets/SQLite/UserWallet.cs | 33 +++------------------------- 1 file changed, 3 insertions(+), 30 deletions(-) diff --git a/src/neo/Wallets/SQLite/UserWallet.cs b/src/neo/Wallets/SQLite/UserWallet.cs index f5b946b010..8fc125c3c5 100644 --- a/src/neo/Wallets/SQLite/UserWallet.cs +++ b/src/neo/Wallets/SQLite/UserWallet.cs @@ -20,7 +20,6 @@ public class UserWallet : Wallet private readonly string path; private readonly byte[] iv; private readonly byte[] masterKey; - private string passphrase; private readonly Dictionary accounts; public override string Name => Path.GetFileNameWithoutExtension(path); @@ -53,7 +52,7 @@ private UserWallet(string path, byte[] passwordKey, bool create) rng.GetBytes(iv); rng.GetBytes(masterKey); } - this.passphrase = CreatePassphrase(passwordKey, iv); + Version version = Assembly.GetExecutingAssembly().GetName().Version; BuildDatabase(); SaveStoredData("PasswordHash", passwordKey.Sha256()); @@ -67,24 +66,11 @@ private UserWallet(string path, byte[] passwordKey, bool create) if (passwordHash != null && !passwordHash.SequenceEqual(passwordKey.Sha256())) throw new CryptographicException(); this.iv = LoadStoredData("IV"); - this.passphrase = CreatePassphrase(passwordKey, iv); this.masterKey = LoadStoredData("MasterKey").AesDecrypt(passwordKey, iv); this.accounts = LoadAccounts(); } } - private string CreatePassphrase(byte[] passwordKey, byte[] key) - { - byte[] xor = new byte[passwordKey.Length]; - - for (int i = 0; i < passwordKey.Length; i++) - { - xor[i] = (byte)(passwordKey[i] ^ key[i % key.Length]); - } - - return xor.ToHexString(); - } - private void AddAccount(UserWalletAccount account, ScryptParameters scrypt) { if (scrypt == null) scrypt = ScryptParameters.Default; @@ -105,6 +91,7 @@ private void AddAccount(UserWalletAccount account, ScryptParameters scrypt) { if (account.HasKey) { + string passphrase = masterKey.ToHexString(); Account db_account = ctx.Accounts.FirstOrDefault(p => p.PublicKeyHash == account.Key.PublicKeyHash.ToArray()); if (db_account == null) { @@ -172,23 +159,8 @@ public bool ChangePassword(string password_old, string password_new) byte[] passwordKey = password_new.ToAesKey(); try { - var newpassphrase = CreatePassphrase(passwordKey, LoadStoredData("IV")); - - lock (db_lock) - using (WalletDataContext ctx = new WalletDataContext(path)) - { - foreach (var account in ctx.Accounts) - { - var key = new KeyPair(GetPrivateKeyFromNEP2(account.Nep2key, passphrase, account.ScryptN, account.ScryptR, account.ScryptP)); - account.Nep2key = key.Export(newpassphrase, account.ScryptN, account.ScryptR, account.ScryptP); - } - ctx.SaveChanges(); - } - - passphrase = newpassphrase; SaveStoredData("PasswordHash", passwordKey.Sha256()); SaveStoredData("MasterKey", masterKey.AesEncrypt(passwordKey, iv)); - return true; } finally @@ -316,6 +288,7 @@ private Dictionary LoadAccounts() { using (WalletDataContext ctx = new WalletDataContext(path)) { + string passphrase = masterKey.ToHexString(); 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)) { From e22bc676cd3d98517befbf1c28c58d41f33ab02f Mon Sep 17 00:00:00 2001 From: Shargon Date: Fri, 14 Feb 2020 18:26:58 +0100 Subject: [PATCH 4/9] Add Salt --- src/neo/Wallets/SQLite/UserWallet.cs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/neo/Wallets/SQLite/UserWallet.cs b/src/neo/Wallets/SQLite/UserWallet.cs index 8fc125c3c5..7731b9e88c 100644 --- a/src/neo/Wallets/SQLite/UserWallet.cs +++ b/src/neo/Wallets/SQLite/UserWallet.cs @@ -11,11 +11,14 @@ using System.Reflection; using System.Security; using System.Security.Cryptography; +using System.Text; namespace Neo.Wallets.SQLite { public class UserWallet : Wallet { + private static readonly byte[] Salt = Encoding.ASCII.GetBytes("NEO"); + private readonly object db_lock = new object(); private readonly string path; private readonly byte[] iv; @@ -41,7 +44,6 @@ public override Version Version private UserWallet(string path, byte[] passwordKey, bool create) { this.path = path; - if (create) { this.iv = new byte[16]; @@ -52,10 +54,9 @@ private UserWallet(string path, byte[] passwordKey, bool create) rng.GetBytes(iv); rng.GetBytes(masterKey); } - Version version = Assembly.GetExecutingAssembly().GetName().Version; BuildDatabase(); - SaveStoredData("PasswordHash", passwordKey.Sha256()); + SaveStoredData("PasswordHash", passwordKey.Concat(Salt).ToArray().Sha256()); SaveStoredData("IV", iv); 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()); @@ -63,7 +64,7 @@ private UserWallet(string path, byte[] passwordKey, bool create) else { 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); @@ -91,7 +92,7 @@ private void AddAccount(UserWalletAccount account, ScryptParameters scrypt) { if (account.HasKey) { - string passphrase = masterKey.ToHexString(); + string passphrase = Encoding.UTF8.GetString(masterKey); Account db_account = ctx.Accounts.FirstOrDefault(p => p.PublicKeyHash == account.Key.PublicKeyHash.ToArray()); if (db_account == null) { @@ -288,7 +289,7 @@ private Dictionary LoadAccounts() { using (WalletDataContext ctx = new WalletDataContext(path)) { - string passphrase = masterKey.ToHexString(); + 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)) { From cffdfb7126ce8d5c5df2a96b5bd509540a0de3d8 Mon Sep 17 00:00:00 2001 From: Shargon Date: Sun, 16 Feb 2020 10:44:10 +0100 Subject: [PATCH 5/9] Unique Scrypt for all wallets --- src/neo/Wallets/SQLite/Account.cs | 4 --- src/neo/Wallets/SQLite/UserWallet.cs | 30 ++++++++++++++------- src/neo/Wallets/SQLite/WalletDataContext.cs | 3 --- 3 files changed, 21 insertions(+), 16 deletions(-) diff --git a/src/neo/Wallets/SQLite/Account.cs b/src/neo/Wallets/SQLite/Account.cs index 9cf3e3d540..1ab9fe2bdb 100644 --- a/src/neo/Wallets/SQLite/Account.cs +++ b/src/neo/Wallets/SQLite/Account.cs @@ -6,9 +6,5 @@ internal class Account { public byte[] PublicKeyHash { get; set; } public string Nep2key { get; set; } - - public int ScryptN { get; set; } = ScryptParameters.Default.N; - public int ScryptR { get; set; } = ScryptParameters.Default.R; - public int ScryptP { get; set; } = ScryptParameters.Default.P; } } diff --git a/src/neo/Wallets/SQLite/UserWallet.cs b/src/neo/Wallets/SQLite/UserWallet.cs index 7731b9e88c..5fcd73e446 100644 --- a/src/neo/Wallets/SQLite/UserWallet.cs +++ b/src/neo/Wallets/SQLite/UserWallet.cs @@ -23,6 +23,7 @@ public class UserWallet : Wallet private readonly string path; private readonly byte[] iv; private readonly byte[] masterKey; + private readonly ScryptParameters scrypt; private readonly Dictionary accounts; public override string Name => Path.GetFileNameWithoutExtension(path); @@ -41,13 +42,22 @@ 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.masterKey = new byte[32]; + this.scrypt = scrypt ?? ScryptParameters.Default; this.accounts = new Dictionary(); using (RandomNumberGenerator rng = RandomNumberGenerator.Create()) { @@ -60,6 +70,9 @@ private UserWallet(string path, byte[] passwordKey, bool create) SaveStoredData("IV", iv); 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(scrypt.N)); + SaveStoredData("ScryptR", BitConverter.GetBytes(scrypt.R)); + SaveStoredData("ScryptP", BitConverter.GetBytes(scrypt.P)); } else { @@ -68,6 +81,12 @@ private UserWallet(string path, byte[] passwordKey, bool create) 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(); } } @@ -98,18 +117,12 @@ private void AddAccount(UserWalletAccount account, ScryptParameters scrypt) { db_account = ctx.Accounts.Add(new Account { - ScryptN = scrypt.N, - ScryptR = scrypt.R, - ScryptP = scrypt.P, Nep2key = account.Key.Export(passphrase, scrypt.N, scrypt.R, scrypt.P), PublicKeyHash = account.Key.PublicKeyHash.ToArray() }).Entity; } else { - db_account.ScryptN = scrypt.N; - db_account.ScryptR = scrypt.R; - db_account.ScryptP = scrypt.P; db_account.Nep2key = account.Key.Export(passphrase, scrypt.N, scrypt.R, scrypt.P); } } @@ -296,8 +309,7 @@ private Dictionary LoadAccounts() VerificationContract contract = db_contract.RawData.AsSerializable(); UserWalletAccount account = accounts[contract.ScriptHash]; account.Contract = contract; - account.Key = new KeyPair(GetPrivateKeyFromNEP2(db_contract.Account.Nep2key, passphrase, - db_contract.Account.ScryptN, db_contract.Account.ScryptR, db_contract.Account.ScryptP)); + account.Key = new KeyPair(GetPrivateKeyFromNEP2(db_contract.Account.Nep2key, passphrase, scrypt.N, scrypt.R, scrypt.P)); } return accounts; } diff --git a/src/neo/Wallets/SQLite/WalletDataContext.cs b/src/neo/Wallets/SQLite/WalletDataContext.cs index 2203602e52..efc22b58d9 100644 --- a/src/neo/Wallets/SQLite/WalletDataContext.cs +++ b/src/neo/Wallets/SQLite/WalletDataContext.cs @@ -33,9 +33,6 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) modelBuilder.Entity().ToTable(nameof(Account)); modelBuilder.Entity().HasKey(p => p.PublicKeyHash); modelBuilder.Entity().Property(p => p.Nep2key).HasColumnType("VarChar").HasMaxLength(byte.MaxValue).IsRequired(); - modelBuilder.Entity().Property(p => p.ScryptN).HasColumnType("Integer").IsRequired(); - modelBuilder.Entity().Property(p => p.ScryptR).HasColumnType("Integer").IsRequired(); - modelBuilder.Entity().Property(p => p.ScryptP).HasColumnType("Integer").IsRequired(); modelBuilder.Entity().Property(p => p.PublicKeyHash).HasColumnType("Binary").HasMaxLength(20).IsRequired(); modelBuilder.Entity
().ToTable(nameof(Address)); modelBuilder.Entity
().HasKey(p => p.ScriptHash); From a4515c9f3ed3f0f0b089e99600abef3a96df14d9 Mon Sep 17 00:00:00 2001 From: Shargon Date: Sun, 16 Feb 2020 10:45:08 +0100 Subject: [PATCH 6/9] Clean using --- src/neo/Wallets/SQLite/Account.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/neo/Wallets/SQLite/Account.cs b/src/neo/Wallets/SQLite/Account.cs index 1ab9fe2bdb..77aef83d93 100644 --- a/src/neo/Wallets/SQLite/Account.cs +++ b/src/neo/Wallets/SQLite/Account.cs @@ -1,5 +1,3 @@ -using Neo.Wallets.NEP6; - namespace Neo.Wallets.SQLite { internal class Account From 3cd4132b19ac2c13bf7b21e32b6cebcbe1fd4b4f Mon Sep 17 00:00:00 2001 From: Shargon Date: Sun, 16 Feb 2020 10:47:49 +0100 Subject: [PATCH 7/9] Clean parameters --- src/neo/Wallets/SQLite/UserWallet.cs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/neo/Wallets/SQLite/UserWallet.cs b/src/neo/Wallets/SQLite/UserWallet.cs index 5fcd73e446..9fca41f875 100644 --- a/src/neo/Wallets/SQLite/UserWallet.cs +++ b/src/neo/Wallets/SQLite/UserWallet.cs @@ -91,10 +91,8 @@ private UserWallet(string path, byte[] passwordKey, bool create, ScryptParameter } } - private void AddAccount(UserWalletAccount account, ScryptParameters scrypt) + private void AddAccount(UserWalletAccount account) { - if (scrypt == null) scrypt = ScryptParameters.Default; - lock (accounts) { if (accounts.TryGetValue(account.ScriptHash, out UserWalletAccount account_old)) @@ -214,7 +212,7 @@ public override WalletAccount CreateAccount(byte[] privateKey) Key = key, Contract = contract }; - AddAccount(account, ScryptParameters.Default); + AddAccount(account); return account; } @@ -234,14 +232,14 @@ public override WalletAccount CreateAccount(SmartContract.Contract contract, Key Key = key, Contract = verification_contract }; - AddAccount(account, ScryptParameters.Default); + AddAccount(account); return account; } public override WalletAccount CreateAccount(UInt160 scriptHash) { UserWalletAccount account = new UserWalletAccount(scriptHash); - AddAccount(account, ScryptParameters.Default); + AddAccount(account); return account; } From 25a74ab9436944e54c465a68e29917744c371222 Mon Sep 17 00:00:00 2001 From: Shargon Date: Sun, 16 Feb 2020 11:18:22 +0100 Subject: [PATCH 8/9] Fix UT --- src/neo/Wallets/SQLite/UserWallet.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/neo/Wallets/SQLite/UserWallet.cs b/src/neo/Wallets/SQLite/UserWallet.cs index 9fca41f875..0c2323d752 100644 --- a/src/neo/Wallets/SQLite/UserWallet.cs +++ b/src/neo/Wallets/SQLite/UserWallet.cs @@ -70,9 +70,9 @@ private UserWallet(string path, byte[] passwordKey, bool create, ScryptParameter SaveStoredData("IV", iv); 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(scrypt.N)); - SaveStoredData("ScryptR", BitConverter.GetBytes(scrypt.R)); - SaveStoredData("ScryptP", BitConverter.GetBytes(scrypt.P)); + SaveStoredData("ScryptN", BitConverter.GetBytes(this.scrypt.N)); + SaveStoredData("ScryptR", BitConverter.GetBytes(this.scrypt.R)); + SaveStoredData("ScryptP", BitConverter.GetBytes(this.scrypt.P)); } else { @@ -360,7 +360,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")); } } } From b162cadda5d660f580bbc489553b260d860342e0 Mon Sep 17 00:00:00 2001 From: Shargon Date: Sun, 16 Feb 2020 16:01:27 +0100 Subject: [PATCH 9/9] Random Salt --- src/neo/Wallets/SQLite/UserWallet.cs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/neo/Wallets/SQLite/UserWallet.cs b/src/neo/Wallets/SQLite/UserWallet.cs index 0c2323d752..36b865a2e3 100644 --- a/src/neo/Wallets/SQLite/UserWallet.cs +++ b/src/neo/Wallets/SQLite/UserWallet.cs @@ -17,11 +17,10 @@ namespace Neo.Wallets.SQLite { public class UserWallet : Wallet { - private static readonly byte[] Salt = Encoding.ASCII.GetBytes("NEO"); - 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; @@ -56,18 +55,21 @@ private UserWallet(string path, byte[] passwordKey, bool create, ScryptParameter 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.Concat(Salt).ToArray().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)); @@ -76,8 +78,9 @@ private UserWallet(string path, byte[] passwordKey, bool create, ScryptParameter } else { + this.salt = LoadStoredData("Salt"); byte[] passwordHash = LoadStoredData("PasswordHash"); - if (passwordHash != null && !passwordHash.SequenceEqual(passwordKey.Concat(Salt).ToArray().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); @@ -360,7 +363,7 @@ private static void SaveStoredData(WalletDataContext ctx, string name, byte[] va public override bool VerifyPassword(string password) { - return password.ToAesKey().Concat(Salt).ToArray().Sha256().SequenceEqual(LoadStoredData("PasswordHash")); + return password.ToAesKey().Concat(salt).ToArray().Sha256().SequenceEqual(LoadStoredData("PasswordHash")); } } }