Merge pull request #2938 from PhilippC/security/audit_suggestions
Security suggestions from Audit
This commit is contained in:
		| @@ -11,10 +11,10 @@ Regular stable releases of Keepass2Android are available on [Google Play](https: | ||||
| Beta-releases can be obtained by opting in to the [Beta testing channel](https://play.google.com/apps/testing/keepass2android.keepass2android) or [Beta testing channel for Keepass2Android Offline](https://play.google.com/apps/testing/keepass2android.keepass2android_nonet). | ||||
|  | ||||
| # How can I contribute? | ||||
| * Help to translate Keepass2Android into your language or improve translations at [our Crowdin page](http://crowdin.net/project/keepass2android) | ||||
| * Help to translate Keepass2Android into your language or improve translations at [our Crowdin page](https://crowdin.net/project/keepass2android) | ||||
| * Add features by [creating a plugin](How-to-create-a-plug-in_.md) or creating a pull request. You might want to contact me before you start working so I can coordinate efforts. | ||||
| * [Become a GitHub sponsor to boost 🚀 development](https://github.com/sponsors/PhilippC) | ||||
| * [Make a donation](http://philipp.crocoll.net/donate.php) | ||||
| * [Make a donation](https://philipp.crocoll.net/donate.php) | ||||
|  | ||||
| # How do I learn more? | ||||
| Please see the [wiki](https://github.com/PhilippC/keepass2android/wiki/Documentation) for further information. | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| /* | ||||
|   KeePass Password Safe - The Open-Source Password Manager | ||||
|   Copyright (C) 2003-2017 Dominik Reichl <dominik.reichl@t-online.de> | ||||
|   Copyright (C) 2003-2025 Dominik Reichl <dominik.reichl@t-online.de> | ||||
|  | ||||
|   This program is free software; you can redistribute it and/or modify | ||||
|   it under the terms of the GNU General Public License as published by | ||||
| @@ -29,197 +29,226 @@ using KeePassLib.Utility; | ||||
|  | ||||
| namespace KeePassLib.Cryptography | ||||
| { | ||||
| 	/// <summary> | ||||
| 	/// Algorithms supported by <c>CryptoRandomStream</c>. | ||||
| 	/// </summary> | ||||
| 	public enum CrsAlgorithm | ||||
| 	{ | ||||
| 		/// <summary> | ||||
| 		/// Not supported. | ||||
| 		/// </summary> | ||||
| 		Null = 0, | ||||
|     /// <summary> | ||||
|     /// Algorithms supported by <c>CryptoRandomStream</c>. | ||||
|     /// </summary> | ||||
|     public enum CrsAlgorithm | ||||
|     { | ||||
|         /// <summary> | ||||
|         /// Not supported. | ||||
|         /// </summary> | ||||
|         Null = 0, | ||||
|  | ||||
| 		/// <summary> | ||||
| 		/// A variant of the ARCFour algorithm (RC4 incompatible). | ||||
| 		/// </summary> | ||||
| 		/// </summary> | ||||
| 		ArcFourVariant = 1, | ||||
|         /// <summary> | ||||
|         /// A variant of the ArcFour algorithm (RC4 incompatible). | ||||
|         /// Insecure; for backward compatibility only. | ||||
|         /// </summary> | ||||
|         ArcFourVariant = 1, | ||||
|  | ||||
| 		/// <summary> | ||||
| 		/// Salsa20 stream cipher algorithm. | ||||
| 		/// </summary> | ||||
| 		Salsa20 = 2, | ||||
|         /// <summary> | ||||
|         /// Salsa20 stream cipher algorithm. | ||||
|         /// </summary> | ||||
|         Salsa20 = 2, | ||||
|  | ||||
| 		/// <summary> | ||||
| 		/// ChaCha20 stream cipher algorithm. | ||||
| 		/// </summary> | ||||
| 		ChaCha20 = 3, | ||||
|         /// <summary> | ||||
|         /// ChaCha20 stream cipher algorithm. | ||||
|         /// </summary> | ||||
|         ChaCha20 = 3, | ||||
|  | ||||
| 		Count = 4 | ||||
| 	} | ||||
|         Count = 4 | ||||
|     } | ||||
|  | ||||
| 	/// <summary> | ||||
| 	/// A random stream class. The class is initialized using random | ||||
| 	/// bytes provided by the caller. The produced stream has random | ||||
| 	/// properties, but for the same seed always the same stream | ||||
| 	/// is produced, i.e. this class can be used as stream cipher. | ||||
| 	/// </summary> | ||||
| 	public sealed class CryptoRandomStream : IDisposable | ||||
| 	{ | ||||
| 		private readonly CrsAlgorithm m_crsAlgorithm; | ||||
|     /// <summary> | ||||
|     /// A random stream class. The class is initialized using random | ||||
|     /// bytes provided by the caller. The produced stream has random | ||||
|     /// properties, but for the same seed always the same stream | ||||
|     /// is produced, i.e. this class can be used as stream cipher. | ||||
|     /// </summary> | ||||
|     public sealed class CryptoRandomStream : IDisposable | ||||
|     { | ||||
|         private readonly CrsAlgorithm m_alg; | ||||
|         private bool m_bDisposed = false; | ||||
|  | ||||
| 		private byte[] m_pbState = null; | ||||
| 		private byte m_i = 0; | ||||
| 		private byte m_j = 0; | ||||
|         private readonly byte[] m_pbKey = null; | ||||
|         private readonly byte[] m_pbIV = null; | ||||
|  | ||||
| 		private Salsa20Cipher m_salsa20 = null; | ||||
| 		private ChaCha20Cipher m_chacha20 = null; | ||||
|         private readonly ChaCha20Cipher m_chacha20 = null; | ||||
|         private readonly Salsa20Cipher m_salsa20 = null; | ||||
|  | ||||
| 		/// <summary> | ||||
| 		/// Construct a new cryptographically secure random stream object. | ||||
| 		/// </summary> | ||||
| 		/// <param name="genAlgorithm">Algorithm to use.</param> | ||||
| 		/// <param name="pbKey">Initialization key. Must not be <c>null</c> and | ||||
| 		/// must contain at least 1 byte.</param> | ||||
| 		public CryptoRandomStream(CrsAlgorithm a, byte[] pbKey) | ||||
| 		{ | ||||
| 			if(pbKey == null) { Debug.Assert(false); throw new ArgumentNullException("pbKey"); } | ||||
| 		/// <exception cref="System.ArgumentNullException">Thrown if the | ||||
| 			int cbKey = pbKey.Length; | ||||
| 			if(cbKey <= 0) | ||||
| 			{ | ||||
| 				Debug.Assert(false); // Need at least one byte | ||||
| 				throw new ArgumentOutOfRangeException("pbKey"); | ||||
| 			} | ||||
| 		/// <paramref name="pbKey" /> parameter is <c>null</c>.</exception> | ||||
| 			m_crsAlgorithm = a; | ||||
| 		/// <exception cref="System.ArgumentException">Thrown if the | ||||
| 			if(a == CrsAlgorithm.ChaCha20) | ||||
| 			{ | ||||
| 				byte[] pbKey32 = new byte[32]; | ||||
| 				byte[] pbIV12 = new byte[12]; | ||||
| 		/// <paramref name="pbKey" /> parameter contains no bytes or the | ||||
| 				using(SHA512Managed h = new SHA512Managed()) | ||||
| 				{ | ||||
| 					byte[] pbHash = h.ComputeHash(pbKey); | ||||
| 					Array.Copy(pbHash, pbKey32, 32); | ||||
| 					Array.Copy(pbHash, 32, pbIV12, 0, 12); | ||||
| 					MemUtil.ZeroByteArray(pbHash); | ||||
| 				} | ||||
| 		/// algorithm is unknown.</exception> | ||||
| 				m_chacha20 = new ChaCha20Cipher(pbKey32, pbIV12, true); | ||||
| 			} | ||||
| 			else if(a == CrsAlgorithm.Salsa20) | ||||
| 		{ | ||||
| 				byte[] pbKey32 = CryptoUtil.HashSha256(pbKey); | ||||
| 				byte[] pbIV8 = new byte[8] { 0xE8, 0x30, 0x09, 0x4B, | ||||
| 					0x97, 0x20, 0x5D, 0x2A }; // Unique constant | ||||
|         private readonly byte[] m_pbState = null; | ||||
|         private byte m_i = 0; | ||||
|         private byte m_j = 0; | ||||
|  | ||||
| 				m_salsa20 = new Salsa20Cipher(pbKey32, pbIV8); | ||||
| 			} | ||||
| 			else if(a == CrsAlgorithm.ArcFourVariant) | ||||
| 			{ | ||||
| 				// Fill the state linearly | ||||
| 				m_pbState = new byte[256]; | ||||
| 				for(int w = 0; w < 256; ++w) m_pbState[w] = (byte)w; | ||||
|         /// <summary> | ||||
|         /// Construct a new cryptographically secure random stream object. | ||||
|         /// </summary> | ||||
|         /// <param name="a">Algorithm to use.</param> | ||||
|         /// <param name="pbKey">Initialization key. Must not be <c>null</c> | ||||
|         /// and must contain at least 1 byte.</param> | ||||
|         public CryptoRandomStream(CrsAlgorithm a, byte[] pbKey) | ||||
|         { | ||||
|             if (pbKey == null) { Debug.Assert(false); throw new ArgumentNullException("pbKey"); } | ||||
|  | ||||
| 				unchecked | ||||
| 				{ | ||||
| 					byte j = 0, t; | ||||
| 					int inxKey = 0; | ||||
| 					for(int w = 0; w < 256; ++w) // Key setup | ||||
| 					{ | ||||
| 						j += (byte)(m_pbState[w] + pbKey[inxKey]); | ||||
|             int cbKey = pbKey.Length; | ||||
|             if (cbKey <= 0) | ||||
|             { | ||||
|                 Debug.Assert(false); // Need at least one byte | ||||
|                 throw new ArgumentOutOfRangeException("pbKey"); | ||||
|             } | ||||
|  | ||||
| 						t = m_pbState[0]; // Swap entries | ||||
| 						m_pbState[0] = m_pbState[j]; | ||||
| 						m_pbState[j] = t; | ||||
|             m_alg = a; | ||||
|  | ||||
| 						++inxKey; | ||||
| 						if(inxKey >= cbKey) inxKey = 0; | ||||
| 					} | ||||
| 				} | ||||
|             if (a == CrsAlgorithm.ChaCha20) | ||||
|             { | ||||
|                 m_pbKey = new byte[32]; | ||||
|                 m_pbIV = new byte[12]; | ||||
|  | ||||
| 				GetRandomBytes(512); // Increases security, see cryptanalysis | ||||
| 			} | ||||
| 			else // Unknown algorithm | ||||
| 			{ | ||||
| 				Debug.Assert(false); | ||||
| 				throw new ArgumentOutOfRangeException("a"); | ||||
| 			} | ||||
| 		} | ||||
|                 using (SHA512Managed h = new SHA512Managed()) | ||||
|                 { | ||||
|                     byte[] pbHash = h.ComputeHash(pbKey); | ||||
|                     Array.Copy(pbHash, m_pbKey, 32); | ||||
|                     Array.Copy(pbHash, 32, m_pbIV, 0, 12); | ||||
|                     MemUtil.ZeroByteArray(pbHash); | ||||
|                 } | ||||
|  | ||||
| 		public void Dispose() | ||||
| 		{ | ||||
| 			Dispose(true); | ||||
| 			GC.SuppressFinalize(this); | ||||
| 		} | ||||
|                 m_chacha20 = new ChaCha20Cipher(m_pbKey, m_pbIV, true); | ||||
|             } | ||||
|             else if (a == CrsAlgorithm.Salsa20) | ||||
|             { | ||||
|                 m_pbKey = CryptoUtil.HashSha256(pbKey); | ||||
|                 m_pbIV = new byte[8] { 0xE8, 0x30, 0x09, 0x4B, | ||||
|                     0x97, 0x20, 0x5D, 0x2A }; // Unique constant | ||||
|  | ||||
| 		private void Dispose(bool disposing) | ||||
| 		{ | ||||
| 			if(disposing) | ||||
| 			{ | ||||
| 				if(m_crsAlgorithm == CrsAlgorithm.ChaCha20) | ||||
| 					m_chacha20.Dispose(); | ||||
| 				else if(m_crsAlgorithm == CrsAlgorithm.Salsa20) | ||||
| 					m_salsa20.Dispose(); | ||||
| 				else if(m_crsAlgorithm == CrsAlgorithm.ArcFourVariant) | ||||
| 				{ | ||||
| 					MemUtil.ZeroByteArray(m_pbState); | ||||
| 					m_i = 0; | ||||
| 					m_j = 0; | ||||
| 				} | ||||
| 				else { Debug.Assert(false); } | ||||
| 			} | ||||
| 		} | ||||
|                 m_salsa20 = new Salsa20Cipher(m_pbKey, m_pbIV); | ||||
|             } | ||||
|             else if (a == CrsAlgorithm.ArcFourVariant) | ||||
|             { | ||||
|                 // Fill the state linearly | ||||
|                 m_pbState = new byte[256]; | ||||
|                 for (int w = 0; w < 256; ++w) m_pbState[w] = (byte)w; | ||||
|  | ||||
| 		/// <summary> | ||||
| 		/// Get <paramref name="uRequestedCount" /> random bytes. | ||||
| 		/// </summary> | ||||
| 		/// <param name="uRequestedCount">Number of random bytes to retrieve.</param> | ||||
| 		/// <returns>Returns <paramref name="uRequestedCount" /> random bytes.</returns> | ||||
| 		public byte[] GetRandomBytes(uint uRequestedCount) | ||||
| 		{ | ||||
| 			if(uRequestedCount == 0) return MemUtil.EmptyByteArray; | ||||
|                 unchecked | ||||
|                 { | ||||
|                     byte j = 0, t; | ||||
|                     int inxKey = 0; | ||||
|                     for (int w = 0; w < 256; ++w) // Key setup | ||||
|                     { | ||||
|                         j += (byte)(m_pbState[w] + pbKey[inxKey]); | ||||
|  | ||||
| 			if(uRequestedCount > (uint)int.MaxValue) | ||||
| 				throw new ArgumentOutOfRangeException("uRequestedCount"); | ||||
| 			int cb = (int)uRequestedCount; | ||||
|                         t = m_pbState[0]; // Swap entries | ||||
|                         m_pbState[0] = m_pbState[j]; | ||||
|                         m_pbState[j] = t; | ||||
|  | ||||
| 			byte[] pbRet = new byte[cb]; | ||||
|                         ++inxKey; | ||||
|                         if (inxKey >= cbKey) inxKey = 0; | ||||
|                     } | ||||
|                 } | ||||
|  | ||||
| 			if(m_crsAlgorithm == CrsAlgorithm.ChaCha20) | ||||
| 				m_chacha20.Encrypt(pbRet, 0, cb); | ||||
| 			else if(m_crsAlgorithm == CrsAlgorithm.Salsa20) | ||||
| 				m_salsa20.Encrypt(pbRet, 0, cb); | ||||
| 			else if(m_crsAlgorithm == CrsAlgorithm.ArcFourVariant) | ||||
| 			{ | ||||
| 				unchecked | ||||
| 				{ | ||||
| 					for(int w = 0; w < cb; ++w) | ||||
| 					{ | ||||
| 						++m_i; | ||||
| 						m_j += m_pbState[m_i]; | ||||
|                 GetRandomBytes(512); // Increases security, see cryptanalysis | ||||
|             } | ||||
|             else // Unknown algorithm | ||||
|             { | ||||
|                 Debug.Assert(false); | ||||
|                 throw new ArgumentOutOfRangeException("a"); | ||||
|             } | ||||
|         } | ||||
|  | ||||
| 						byte t = m_pbState[m_i]; // Swap entries | ||||
| 						m_pbState[m_i] = m_pbState[m_j]; | ||||
| 						m_pbState[m_j] = t; | ||||
|         public void Dispose() | ||||
|         { | ||||
|             Dispose(true); | ||||
|             GC.SuppressFinalize(this); | ||||
|         } | ||||
|  | ||||
| 						t = (byte)(m_pbState[m_i] + m_pbState[m_j]); | ||||
| 						pbRet[w] = m_pbState[t]; | ||||
| 					} | ||||
| 				} | ||||
| 			} | ||||
| 			else { Debug.Assert(false); } | ||||
|         private void Dispose(bool disposing) | ||||
|         { | ||||
|             if (disposing) | ||||
|             { | ||||
|                 if (m_alg == CrsAlgorithm.ChaCha20) | ||||
|                     m_chacha20.Dispose(); | ||||
|                 else if (m_alg == CrsAlgorithm.Salsa20) | ||||
|                     m_salsa20.Dispose(); | ||||
|                 else if (m_alg == CrsAlgorithm.ArcFourVariant) | ||||
|                 { | ||||
|                     MemUtil.ZeroByteArray(m_pbState); | ||||
|                     m_i = 0; | ||||
|                     m_j = 0; | ||||
|                 } | ||||
|                 else { Debug.Assert(false); } | ||||
|  | ||||
| 			return pbRet; | ||||
| 		} | ||||
|                 if (m_pbKey != null) MemUtil.ZeroByteArray(m_pbKey); | ||||
|                 if (m_pbIV != null) MemUtil.ZeroByteArray(m_pbIV); | ||||
|  | ||||
| 		public ulong GetRandomUInt64() | ||||
| 		{ | ||||
| 			byte[] pb = GetRandomBytes(8); | ||||
| 			return MemUtil.BytesToUInt64(pb); | ||||
| 			} | ||||
|                 m_bDisposed = true; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Get <paramref name="uRequestedCount" /> random bytes. | ||||
|         /// </summary> | ||||
|         /// <param name="uRequestedCount">Number of random bytes to retrieve.</param> | ||||
|         /// <returns>Returns <paramref name="uRequestedCount" /> random bytes.</returns> | ||||
|         public byte[] GetRandomBytes(uint uRequestedCount) | ||||
|         { | ||||
|             if (m_bDisposed) throw new ObjectDisposedException(null); | ||||
|  | ||||
|             if (uRequestedCount == 0) return MemUtil.EmptyByteArray; | ||||
|             if (uRequestedCount > (uint)int.MaxValue) | ||||
|                 throw new ArgumentOutOfRangeException("uRequestedCount"); | ||||
|             int cb = (int)uRequestedCount; | ||||
|  | ||||
|             byte[] pbRet = new byte[cb]; | ||||
|  | ||||
|             if (m_alg == CrsAlgorithm.ChaCha20) | ||||
|                 m_chacha20.Encrypt(pbRet, 0, cb); | ||||
|             else if (m_alg == CrsAlgorithm.Salsa20) | ||||
|                 m_salsa20.Encrypt(pbRet, 0, cb); | ||||
|             else if (m_alg == CrsAlgorithm.ArcFourVariant) | ||||
|             { | ||||
|                 unchecked | ||||
|                 { | ||||
|                     for (int w = 0; w < cb; ++w) | ||||
|                     { | ||||
|                         ++m_i; | ||||
|                         m_j += m_pbState[m_i]; | ||||
|  | ||||
|                         byte t = m_pbState[m_i]; // Swap entries | ||||
|                         m_pbState[m_i] = m_pbState[m_j]; | ||||
|                         m_pbState[m_j] = t; | ||||
|  | ||||
|                         t = (byte)(m_pbState[m_i] + m_pbState[m_j]); | ||||
|                         pbRet[w] = m_pbState[t]; | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|             else { Debug.Assert(false); } | ||||
|  | ||||
|             return pbRet; | ||||
|         } | ||||
|  | ||||
|         public ulong GetRandomUInt64() | ||||
|         { | ||||
|             byte[] pb = GetRandomBytes(8); | ||||
|             return MemUtil.BytesToUInt64(pb); | ||||
|         } | ||||
|  | ||||
|         internal ulong GetRandomUInt64(ulong uMaxExcl) | ||||
|         { | ||||
|             if (uMaxExcl == 0) { Debug.Assert(false); throw new ArgumentOutOfRangeException("uMaxExcl"); } | ||||
|  | ||||
|             ulong uGen, uRem; | ||||
|             do | ||||
|             { | ||||
|                 uGen = GetRandomUInt64(); | ||||
|                 uRem = uGen % uMaxExcl; | ||||
|             } | ||||
|             while ((uGen - uRem) > (ulong.MaxValue - (uMaxExcl - 1UL))); | ||||
|             // This ensures that the last number of the block (i.e. | ||||
|             // (uGen - uRem) + (uMaxExcl - 1)) is generatable; | ||||
|             // for signed longs, overflow to negative number: | ||||
|             // while((uGen - uRem) + (uMaxExcl - 1) < 0); | ||||
|  | ||||
|             return uRem; | ||||
|         } | ||||
|  | ||||
| #if CRSBENCHMARK | ||||
| 		public static string Benchmark() | ||||
| @@ -237,22 +266,21 @@ namespace KeePassLib.Cryptography | ||||
| 			return str; | ||||
| 		} | ||||
|  | ||||
| 		private static int BenchTime(CrsAlgorithm cra, int nRounds, int nDataSize) | ||||
| 		private static int BenchTime(CrsAlgorithm a, int nRounds, int cbData) | ||||
| 		{ | ||||
| 			byte[] pbKey = new byte[4] { 0x00, 0x01, 0x02, 0x03 }; | ||||
|  | ||||
| 			int nStart = Environment.TickCount; | ||||
| 			int tStart = Environment.TickCount; | ||||
| 			for(int i = 0; i < nRounds; ++i) | ||||
| 			{ | ||||
| 				using(CryptoRandomStream c = new CryptoRandomStream(cra, pbKey)) | ||||
| 				using(CryptoRandomStream crs = new CryptoRandomStream(a, pbKey)) | ||||
| 				{ | ||||
| 					c.GetRandomBytes((uint)nDataSize); | ||||
| 					crs.GetRandomBytes((uint)cbData); | ||||
| 				} | ||||
| 			} | ||||
| 			int nEnd = Environment.TickCount; | ||||
|  | ||||
| 			return (nEnd - nStart); | ||||
| 			return (Environment.TickCount - tStart); | ||||
| 		} | ||||
| #endif | ||||
| 	} | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -1,65 +0,0 @@ | ||||
| /* | ||||
|   KeePass Password Safe - The Open-Source Password Manager | ||||
|   Copyright (C) 2003-2017 Dominik Reichl <dominik.reichl@t-online.de> | ||||
|  | ||||
|   This program is free software; you can redistribute it and/or modify | ||||
|   it under the terms of the GNU General Public License as published by | ||||
|   the Free Software Foundation; either version 2 of the License, or | ||||
|   (at your option) any later version. | ||||
|  | ||||
|   This program is distributed in the hope that it will be useful, | ||||
|   but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
|   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
|   GNU General Public License for more details. | ||||
|  | ||||
|   You should have received a copy of the GNU General Public License | ||||
|   along with this program; if not, write to the Free Software | ||||
|   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA | ||||
| */ | ||||
|  | ||||
| using System; | ||||
| using System.Collections.Generic; | ||||
| using System.Text; | ||||
| using System.Diagnostics; | ||||
|  | ||||
| using KeePassLib.Security; | ||||
| using KeePassLib.Utility; | ||||
|  | ||||
| namespace KeePassLib.Cryptography.PasswordGenerator | ||||
| { | ||||
| 	internal static class CharSetBasedGenerator | ||||
| 	{ | ||||
| 		internal static PwgError Generate(out ProtectedString psOut, | ||||
| 			PwProfile pwProfile, CryptoRandomStream crsRandomSource) | ||||
| 		{ | ||||
| 			psOut = ProtectedString.Empty; | ||||
| 			if(pwProfile.Length == 0) return PwgError.Success; | ||||
|  | ||||
| 			PwCharSet pcs = new PwCharSet(pwProfile.CharSet.ToString()); | ||||
| 			char[] vGenerated = new char[pwProfile.Length]; | ||||
|  | ||||
| 			PwGenerator.PrepareCharSet(pcs, pwProfile); | ||||
|  | ||||
| 			for(int nIndex = 0; nIndex < (int)pwProfile.Length; ++nIndex) | ||||
| 			{ | ||||
| 				char ch = PwGenerator.GenerateCharacter(pwProfile, pcs, | ||||
| 					crsRandomSource); | ||||
|  | ||||
| 				if(ch == char.MinValue) | ||||
| 				{ | ||||
| 					MemUtil.ZeroArray<char>(vGenerated); | ||||
| 					return PwgError.TooFewCharacters; | ||||
| 				} | ||||
|  | ||||
| 				vGenerated[nIndex] = ch; | ||||
| 			} | ||||
|  | ||||
| 			byte[] pbUtf8 = StrUtil.Utf8.GetBytes(vGenerated); | ||||
| 			psOut = new ProtectedString(true, pbUtf8); | ||||
| 			MemUtil.ZeroByteArray(pbUtf8); | ||||
| 			MemUtil.ZeroArray<char>(vGenerated); | ||||
|  | ||||
| 			return PwgError.Success; | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| @@ -1,173 +0,0 @@ | ||||
| /* | ||||
|   KeePass Password Safe - The Open-Source Password Manager | ||||
|   Copyright (C) 2003-2017 Dominik Reichl <dominik.reichl@t-online.de> | ||||
|  | ||||
|   This program is free software; you can redistribute it and/or modify | ||||
|   it under the terms of the GNU General Public License as published by | ||||
|   the Free Software Foundation; either version 2 of the License, or | ||||
|   (at your option) any later version. | ||||
|  | ||||
|   This program is distributed in the hope that it will be useful, | ||||
|   but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||
|   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||
|   GNU General Public License for more details. | ||||
|  | ||||
|   You should have received a copy of the GNU General Public License | ||||
|   along with this program; if not, write to the Free Software | ||||
|   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA | ||||
| */ | ||||
|  | ||||
| using System; | ||||
| using System.Collections.Generic; | ||||
| using System.Text; | ||||
| using System.Diagnostics; | ||||
|  | ||||
| using KeePassLib.Security; | ||||
| using KeePassLib.Utility; | ||||
|  | ||||
| namespace KeePassLib.Cryptography.PasswordGenerator | ||||
| { | ||||
| 	internal static class PatternBasedGenerator | ||||
| 	{ | ||||
| 		internal static PwgError Generate(out ProtectedString psOut, | ||||
| 			PwProfile pwProfile, CryptoRandomStream crsRandomSource) | ||||
| 		{ | ||||
| 			psOut = ProtectedString.Empty; | ||||
| 			LinkedList<char> vGenerated = new LinkedList<char>(); | ||||
| 			PwCharSet pcsCurrent = new PwCharSet(); | ||||
| 			PwCharSet pcsCustom = new PwCharSet(); | ||||
| 			PwCharSet pcsUsed = new PwCharSet(); | ||||
| 			bool bInCharSetDef = false; | ||||
|  | ||||
| 			string strPattern = ExpandPattern(pwProfile.Pattern); | ||||
| 			if(strPattern.Length == 0) return PwgError.Success; | ||||
|  | ||||
| 			CharStream csStream = new CharStream(strPattern); | ||||
| 			char ch = csStream.ReadChar(); | ||||
|  | ||||
| 			while(ch != char.MinValue) | ||||
| 			{ | ||||
| 				pcsCurrent.Clear(); | ||||
|  | ||||
| 				bool bGenerateChar = false; | ||||
|  | ||||
| 				if(ch == '\\') | ||||
| 				{ | ||||
| 					ch = csStream.ReadChar(); | ||||
| 					if(ch == char.MinValue) // Backslash at the end | ||||
| 					{ | ||||
| 						vGenerated.AddLast('\\'); | ||||
| 						break; | ||||
| 					} | ||||
|  | ||||
| 					if(bInCharSetDef) pcsCustom.Add(ch); | ||||
| 					else | ||||
| 					{ | ||||
| 						vGenerated.AddLast(ch); | ||||
| 						pcsUsed.Add(ch); | ||||
| 					} | ||||
| 				} | ||||
| 				else if(ch == '[') | ||||
| 				{ | ||||
| 					pcsCustom.Clear(); | ||||
| 					bInCharSetDef = true; | ||||
| 				} | ||||
| 				else if(ch == ']') | ||||
| 				{ | ||||
| 					pcsCurrent.Add(pcsCustom.ToString()); | ||||
|  | ||||
| 					bInCharSetDef = false; | ||||
| 					bGenerateChar = true; | ||||
| 				} | ||||
| 				else if(bInCharSetDef) | ||||
| 				{ | ||||
| 					if(pcsCustom.AddCharSet(ch) == false) | ||||
| 						pcsCustom.Add(ch); | ||||
| 				} | ||||
| 				else if(pcsCurrent.AddCharSet(ch) == false) | ||||
| 				{ | ||||
| 					vGenerated.AddLast(ch); | ||||
| 					pcsUsed.Add(ch); | ||||
| 				} | ||||
| 				else bGenerateChar = true; | ||||
|  | ||||
| 				if(bGenerateChar) | ||||
| 				{ | ||||
| 					PwGenerator.PrepareCharSet(pcsCurrent, pwProfile); | ||||
|  | ||||
| 					if(pwProfile.NoRepeatingCharacters) | ||||
| 						pcsCurrent.Remove(pcsUsed.ToString()); | ||||
|  | ||||
| 					char chGen = PwGenerator.GenerateCharacter(pwProfile, | ||||
| 						pcsCurrent, crsRandomSource); | ||||
|  | ||||
| 					if(chGen == char.MinValue) return PwgError.TooFewCharacters; | ||||
|  | ||||
| 					vGenerated.AddLast(chGen); | ||||
| 					pcsUsed.Add(chGen); | ||||
| 				} | ||||
|  | ||||
| 				ch = csStream.ReadChar(); | ||||
| 			} | ||||
|  | ||||
| 			if(vGenerated.Count == 0) return PwgError.Success; | ||||
|  | ||||
| 			char[] vArray = new char[vGenerated.Count]; | ||||
| 			vGenerated.CopyTo(vArray, 0); | ||||
|  | ||||
| 			if(pwProfile.PatternPermutePassword) | ||||
| 				PwGenerator.ShufflePassword(vArray, crsRandomSource); | ||||
|  | ||||
| 			byte[] pbUtf8 = StrUtil.Utf8.GetBytes(vArray); | ||||
| 			psOut = new ProtectedString(true, pbUtf8); | ||||
| 			MemUtil.ZeroByteArray(pbUtf8); | ||||
| 			MemUtil.ZeroArray<char>(vArray); | ||||
| 			vGenerated.Clear(); | ||||
|  | ||||
| 			return PwgError.Success; | ||||
| 		} | ||||
|  | ||||
| 		private static string ExpandPattern(string strPattern) | ||||
| 		{ | ||||
| 			Debug.Assert(strPattern != null); if(strPattern == null) return string.Empty; | ||||
| 			string str = strPattern; | ||||
|  | ||||
| 			while(true) | ||||
| 			{ | ||||
| 				int nOpen = FindFirstUnescapedChar(str, '{'); | ||||
| 				int nClose = FindFirstUnescapedChar(str, '}'); | ||||
|  | ||||
| 				if((nOpen >= 0) && (nOpen < nClose)) | ||||
| 				{ | ||||
| 					string strCount = str.Substring(nOpen + 1, nClose - nOpen - 1); | ||||
| 					str = str.Remove(nOpen, nClose - nOpen + 1); | ||||
|  | ||||
| 					uint uRepeat; | ||||
| 					if(StrUtil.TryParseUInt(strCount, out uRepeat) && (nOpen >= 1)) | ||||
| 					{ | ||||
| 						if(uRepeat == 0) | ||||
| 							str = str.Remove(nOpen - 1, 1); | ||||
| 						else | ||||
| 							str = str.Insert(nOpen, new string(str[nOpen - 1], (int)uRepeat - 1)); | ||||
| 					} | ||||
| 				} | ||||
| 				else break; | ||||
| 			} | ||||
|  | ||||
| 			return str; | ||||
| 		} | ||||
|  | ||||
| 		private static int FindFirstUnescapedChar(string str, char ch) | ||||
| 		{ | ||||
| 			for(int i = 0; i < str.Length; ++i) | ||||
| 			{ | ||||
| 				char chCur = str[i]; | ||||
|  | ||||
| 				if(chCur == '\\') ++i; // Next is escaped, skip it | ||||
| 				else if(chCur == ch) return i; | ||||
| 			} | ||||
|  | ||||
| 			return -1; | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| @@ -1,6 +1,6 @@ | ||||
| /* | ||||
|   KeePass Password Safe - The Open-Source Password Manager | ||||
|   Copyright (C) 2003-2017 Dominik Reichl <dominik.reichl@t-online.de> | ||||
|   Copyright (C) 2003-2025 Dominik Reichl <dominik.reichl@t-online.de> | ||||
|  | ||||
|   This program is free software; you can redistribute it and/or modify | ||||
|   it under the terms of the GNU General Public License as published by | ||||
| @@ -19,333 +19,311 @@ | ||||
|  | ||||
| using System; | ||||
| using System.Collections.Generic; | ||||
| using System.Text; | ||||
| using System.Diagnostics; | ||||
| using System.Text; | ||||
|  | ||||
| using KeePassLib.Utility; | ||||
|  | ||||
| namespace KeePassLib.Cryptography.PasswordGenerator | ||||
| { | ||||
| 	public sealed class PwCharSet | ||||
| 	{ | ||||
| 		public const string UpperCase = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; | ||||
| 		public const string LowerCase = "abcdefghijklmnopqrstuvwxyz"; | ||||
| 		public const string Digits = "0123456789"; | ||||
|     public sealed class PwCharSet : IEquatable<PwCharSet> | ||||
|     { | ||||
|         public static readonly string UpperCase = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; | ||||
|         public static readonly string LowerCase = "abcdefghijklmnopqrstuvwxyz"; | ||||
|         public static readonly string Digits = "0123456789"; | ||||
|  | ||||
| 		public const string UpperConsonants = "BCDFGHJKLMNPQRSTVWXYZ"; | ||||
| 		public const string LowerConsonants = "bcdfghjklmnpqrstvwxyz"; | ||||
| 		public const string UpperVowels = "AEIOU"; | ||||
| 		public const string LowerVowels = "aeiou"; | ||||
|         public static readonly string UpperConsonants = "BCDFGHJKLMNPQRSTVWXYZ"; | ||||
|         public static readonly string LowerConsonants = "bcdfghjklmnpqrstvwxyz"; | ||||
|         public static readonly string UpperVowels = "AEIOU"; | ||||
|         public static readonly string LowerVowels = "aeiou"; | ||||
|  | ||||
| 		public const string Punctuation = @",.;:"; | ||||
| 		public const string Brackets = @"[]{}()<>"; | ||||
|         public static readonly string Punctuation = ",.;:"; | ||||
|         public static readonly string Brackets = @"[]{}()<>"; | ||||
|  | ||||
| 		public const string PrintableAsciiSpecial = "!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~"; | ||||
|         public static readonly string Special = "!\"#$%&'*+,./:;=?@\\^`|~"; | ||||
|         public static readonly string PrintableAsciiSpecial = "!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~"; | ||||
|  | ||||
| 		public const string UpperHex = "0123456789ABCDEF"; | ||||
| 		public const string LowerHex = "0123456789abcdef"; | ||||
|         public static readonly string UpperHex = "0123456789ABCDEF"; | ||||
|         public static readonly string LowerHex = "0123456789abcdef"; | ||||
|  | ||||
| 		public const string Invalid = "\t\r\n"; | ||||
| 		public const string LookAlike = @"O0l1I|"; | ||||
|         public static readonly string LookAlike = "O0Il1|"; | ||||
|  | ||||
| 		internal const string MenuAccels = PwCharSet.LowerCase + PwCharSet.Digits; | ||||
|         /// <summary> | ||||
|         /// Latin-1 Supplement except U+00A0 (NBSP) and U+00AD (SHY). | ||||
|         /// </summary> | ||||
|         public static readonly string Latin1S = | ||||
|             "\u00A1\u00A2\u00A3\u00A4\u00A5\u00A6\u00A7" + | ||||
|             "\u00A8\u00A9\u00AA\u00AB\u00AC\u00AE\u00AF" + | ||||
|             "\u00B0\u00B1\u00B2\u00B3\u00B4\u00B5\u00B6\u00B7" + | ||||
|             "\u00B8\u00B9\u00BA\u00BB\u00BC\u00BD\u00BE\u00BF" + | ||||
|             "\u00C0\u00C1\u00C2\u00C3\u00C4\u00C5\u00C6\u00C7" + | ||||
|             "\u00C8\u00C9\u00CA\u00CB\u00CC\u00CD\u00CE\u00CF" + | ||||
|             "\u00D0\u00D1\u00D2\u00D3\u00D4\u00D5\u00D6\u00D7" + | ||||
|             "\u00D8\u00D9\u00DA\u00DB\u00DC\u00DD\u00DE\u00DF" + | ||||
|             "\u00E0\u00E1\u00E2\u00E3\u00E4\u00E5\u00E6\u00E7" + | ||||
|             "\u00E8\u00E9\u00EA\u00EB\u00EC\u00ED\u00EE\u00EF" + | ||||
|             "\u00F0\u00F1\u00F2\u00F3\u00F4\u00F5\u00F6\u00F7" + | ||||
|             "\u00F8\u00F9\u00FA\u00FB\u00FC\u00FD\u00FE\u00FF"; | ||||
|  | ||||
| 		private const int CharTabSize = (0x10000 / 8); | ||||
|         // internal static readonly string MenuAccels = PwCharSet.LowerCase + PwCharSet.Digits; | ||||
|  | ||||
| 		private List<char> m_vChars = new List<char>(); | ||||
| 		private byte[] m_vTab = new byte[CharTabSize]; | ||||
|         [Obsolete] | ||||
|         public static string SpecialChars { get { return PwCharSet.Special; } } | ||||
|         [Obsolete] | ||||
|         public static string HighAnsiChars { get { return PwCharSet.Latin1S; } } | ||||
|  | ||||
| 		private static string m_strHighAnsi = null; | ||||
| 		public static string HighAnsiChars | ||||
| 		{ | ||||
| 			get | ||||
| 			{ | ||||
| 				if(m_strHighAnsi == null) { new PwCharSet(); } // Create string | ||||
| 				Debug.Assert(m_strHighAnsi != null); | ||||
| 				return m_strHighAnsi; | ||||
| 			} | ||||
| 		} | ||||
|         private readonly List<char> m_lChars = new List<char>(); | ||||
|         private readonly byte[] m_vTab = new byte[0x10000 / 8]; | ||||
|  | ||||
| 		private static string m_strSpecial = null; | ||||
| 		public static string SpecialChars | ||||
| 		{ | ||||
| 			get | ||||
| 			{ | ||||
| 				if(m_strSpecial == null) { new PwCharSet(); } // Create string | ||||
| 				Debug.Assert(m_strSpecial != null); | ||||
| 				return m_strSpecial; | ||||
| 			} | ||||
| 		} | ||||
|         /// <summary> | ||||
|         /// Create a new, empty character set. | ||||
|         /// </summary> | ||||
|         public PwCharSet() | ||||
|         { | ||||
|             Debug.Assert(PwCharSet.Latin1S.Length == (16 * 6 - 2)); | ||||
|         } | ||||
|  | ||||
| 		/// <summary> | ||||
| 		/// Create a new, empty character set collection object. | ||||
| 		/// </summary> | ||||
| 		public PwCharSet() | ||||
| 		{ | ||||
| 			Initialize(true); | ||||
| 		} | ||||
|         public PwCharSet(string strCharSet) | ||||
|         { | ||||
|             Add(strCharSet); | ||||
|         } | ||||
|  | ||||
| 		public PwCharSet(string strCharSet) | ||||
| 		{ | ||||
| 			Initialize(true); | ||||
| 			Add(strCharSet); | ||||
| 		} | ||||
|         /// <summary> | ||||
|         /// Number of characters in this set. | ||||
|         /// </summary> | ||||
|         public uint Size | ||||
|         { | ||||
|             get { return (uint)m_lChars.Count; } | ||||
|         } | ||||
|  | ||||
| 		private PwCharSet(bool bFullInitialize) | ||||
| 		{ | ||||
| 			Initialize(bFullInitialize); | ||||
| 		} | ||||
|         /// <summary> | ||||
|         /// Get a character of the set using an index. | ||||
|         /// </summary> | ||||
|         /// <param name="uPos">Index of the character to get.</param> | ||||
|         /// <returns>Character at the specified position. If the index is invalid, | ||||
|         /// an <c>ArgumentOutOfRangeException</c> is thrown.</returns> | ||||
|         public char this[uint uPos] | ||||
|         { | ||||
|             get | ||||
|             { | ||||
|                 if (uPos >= (uint)m_lChars.Count) | ||||
|                     throw new ArgumentOutOfRangeException("uPos"); | ||||
|  | ||||
| 		private void Initialize(bool bFullInitialize) | ||||
| 		{ | ||||
| 			Clear(); | ||||
|                 return m_lChars[(int)uPos]; | ||||
|             } | ||||
|         } | ||||
|  | ||||
| 			if(!bFullInitialize) return; | ||||
|         public bool Equals(PwCharSet other) | ||||
|         { | ||||
|             if (object.ReferenceEquals(other, this)) return true; | ||||
|             if (object.ReferenceEquals(other, null)) return false; | ||||
|  | ||||
| 			if(m_strHighAnsi == null) | ||||
| 			{ | ||||
| 				StringBuilder sbHighAnsi = new StringBuilder(); | ||||
| 				// [U+0080, U+009F] are C1 control characters, | ||||
| 				// U+00A0 is non-breaking space | ||||
| 				for(char ch = '\u00A1'; ch <= '\u00AC'; ++ch) | ||||
| 					sbHighAnsi.Append(ch); | ||||
| 				// U+00AD is soft hyphen (format character) | ||||
| 				for(char ch = '\u00AE'; ch < '\u00FF'; ++ch) | ||||
| 					sbHighAnsi.Append(ch); | ||||
| 				sbHighAnsi.Append('\u00FF'); | ||||
|             if (m_lChars.Count != other.m_lChars.Count) return false; | ||||
|  | ||||
| 				m_strHighAnsi = sbHighAnsi.ToString(); | ||||
| 			} | ||||
|             return MemUtil.ArraysEqual(m_vTab, other.m_vTab); | ||||
|         } | ||||
|  | ||||
| 			if(m_strSpecial == null) | ||||
| 			{ | ||||
| 				PwCharSet pcs = new PwCharSet(false); | ||||
| 				pcs.AddRange('!', '/'); | ||||
| 				pcs.AddRange(':', '@'); | ||||
| 				pcs.AddRange('[', '`'); | ||||
| 				pcs.Add(@"|~"); | ||||
| 				pcs.Remove(@"-_ "); | ||||
| 				pcs.Remove(PwCharSet.Brackets); | ||||
|         public override bool Equals(object obj) | ||||
|         { | ||||
|             return Equals(obj as PwCharSet); | ||||
|         } | ||||
|  | ||||
| 				m_strSpecial = pcs.ToString(); | ||||
| 			} | ||||
| 		} | ||||
|         public override int GetHashCode() | ||||
|         { | ||||
|             return (int)MemUtil.Hash32(m_vTab, 0, m_vTab.Length); | ||||
|         } | ||||
|  | ||||
| 		/// <summary> | ||||
| 		/// Number of characters in this set. | ||||
| 		/// </summary> | ||||
| 		public uint Size | ||||
| 		{ | ||||
| 			get { return (uint)m_vChars.Count; } | ||||
| 		} | ||||
|         /// <summary> | ||||
|         /// Remove all characters from this set. | ||||
|         /// </summary> | ||||
|         public void Clear() | ||||
|         { | ||||
|             m_lChars.Clear(); | ||||
|             Array.Clear(m_vTab, 0, m_vTab.Length); | ||||
|         } | ||||
|  | ||||
| 		/// <summary> | ||||
| 		/// Get a character of the set using an index. | ||||
| 		/// </summary> | ||||
| 		/// <param name="uPos">Index of the character to get.</param> | ||||
| 		/// <returns>Character at the specified position. If the index is invalid, | ||||
| 		/// an <c>ArgumentOutOfRangeException</c> is thrown.</returns> | ||||
| 		public char this[uint uPos] | ||||
| 		{ | ||||
| 			get | ||||
| 			{ | ||||
| 				if(uPos >= (uint)m_vChars.Count) | ||||
| 					throw new ArgumentOutOfRangeException("uPos"); | ||||
|         public bool Contains(char ch) | ||||
|         { | ||||
|             return (((m_vTab[ch / 8] >> (ch % 8)) & 1) != char.MinValue); | ||||
|         } | ||||
|  | ||||
| 				return m_vChars[(int)uPos]; | ||||
| 			} | ||||
| 		} | ||||
|         public bool Contains(string strCharacters) | ||||
|         { | ||||
|             Debug.Assert(strCharacters != null); | ||||
|             if (strCharacters == null) throw new ArgumentNullException("strCharacters"); | ||||
|  | ||||
| 		/// <summary> | ||||
| 		/// Remove all characters from this set. | ||||
| 		/// </summary> | ||||
| 		public void Clear() | ||||
| 		{ | ||||
| 			m_vChars.Clear(); | ||||
| 			Array.Clear(m_vTab, 0, m_vTab.Length); | ||||
| 		} | ||||
|             foreach (char ch in strCharacters) | ||||
|             { | ||||
|                 if (!Contains(ch)) return false; | ||||
|             } | ||||
|  | ||||
| 		public bool Contains(char ch) | ||||
| 		{ | ||||
| 			return (((m_vTab[ch / 8] >> (ch % 8)) & 1) != char.MinValue); | ||||
| 		} | ||||
|             return true; | ||||
|         } | ||||
|  | ||||
| 		public bool Contains(string strCharacters) | ||||
| 		{ | ||||
| 			Debug.Assert(strCharacters != null); | ||||
| 			if(strCharacters == null) throw new ArgumentNullException("strCharacters"); | ||||
|         /// <summary> | ||||
|         /// Add characters to the set. | ||||
|         /// </summary> | ||||
|         /// <param name="ch">Character to add.</param> | ||||
|         public void Add(char ch) | ||||
|         { | ||||
|             if (ch == char.MinValue) { Debug.Assert(false); return; } | ||||
|  | ||||
| 			foreach(char ch in strCharacters) | ||||
| 			{ | ||||
| 				if(!Contains(ch)) return false; | ||||
| 			} | ||||
|             if (!Contains(ch)) | ||||
|             { | ||||
|                 m_lChars.Add(ch); | ||||
|                 m_vTab[ch / 8] |= (byte)(1 << (ch % 8)); | ||||
|             } | ||||
|         } | ||||
|  | ||||
| 			return true; | ||||
| 		} | ||||
|         /// <summary> | ||||
|         /// Add characters to the set. | ||||
|         /// </summary> | ||||
|         /// <param name="strCharSet">String containing characters to add.</param> | ||||
|         public void Add(string strCharSet) | ||||
|         { | ||||
|             Debug.Assert(strCharSet != null); | ||||
|             if (strCharSet == null) throw new ArgumentNullException("strCharSet"); | ||||
|  | ||||
| 		/// <summary> | ||||
| 		/// Add characters to the set. | ||||
| 		/// </summary> | ||||
| 		/// <param name="ch">Character to add.</param> | ||||
| 		public void Add(char ch) | ||||
| 		{ | ||||
| 			if(ch == char.MinValue) { Debug.Assert(false); return; } | ||||
|             foreach (char ch in strCharSet) | ||||
|                 Add(ch); | ||||
|         } | ||||
|  | ||||
| 			if(!Contains(ch)) | ||||
| 			{ | ||||
| 				m_vChars.Add(ch); | ||||
| 				m_vTab[ch / 8] |= (byte)(1 << (ch % 8)); | ||||
| 			} | ||||
| 		} | ||||
|         public void Add(string strCharSet1, string strCharSet2) | ||||
|         { | ||||
|             Add(strCharSet1); | ||||
|             Add(strCharSet2); | ||||
|         } | ||||
|  | ||||
| 		/// <summary> | ||||
| 		/// Add characters to the set. | ||||
| 		/// </summary> | ||||
| 		/// <param name="strCharSet">String containing characters to add.</param> | ||||
| 		public void Add(string strCharSet) | ||||
| 		{ | ||||
| 			Debug.Assert(strCharSet != null); | ||||
| 			if(strCharSet == null) throw new ArgumentNullException("strCharSet"); | ||||
|         public void Add(string strCharSet1, string strCharSet2, string strCharSet3) | ||||
|         { | ||||
|             Add(strCharSet1); | ||||
|             Add(strCharSet2); | ||||
|             Add(strCharSet3); | ||||
|         } | ||||
|  | ||||
| 			m_vChars.Capacity = m_vChars.Count + strCharSet.Length; | ||||
|         public void AddRange(char chMin, char chMax) | ||||
|         { | ||||
|             for (char ch = chMin; ch < chMax; ++ch) | ||||
|                 Add(ch); | ||||
|  | ||||
| 			foreach(char ch in strCharSet) | ||||
| 				Add(ch); | ||||
| 		} | ||||
|             Add(chMax); | ||||
|         } | ||||
|  | ||||
| 		public void Add(string strCharSet1, string strCharSet2) | ||||
| 		{ | ||||
| 			Add(strCharSet1); | ||||
| 			Add(strCharSet2); | ||||
| 		} | ||||
|         public bool AddCharSet(char chCharSetIdentifier) | ||||
|         { | ||||
|             bool bResult = true; | ||||
|  | ||||
| 		public void Add(string strCharSet1, string strCharSet2, string strCharSet3) | ||||
| 		{ | ||||
| 			Add(strCharSet1); | ||||
| 			Add(strCharSet2); | ||||
| 			Add(strCharSet3); | ||||
| 		} | ||||
|             switch (chCharSetIdentifier) | ||||
|             { | ||||
|                 case 'a': Add(PwCharSet.LowerCase, PwCharSet.Digits); break; | ||||
|                 case 'A': | ||||
|                     Add(PwCharSet.LowerCase, PwCharSet.UpperCase, | ||||
|                     PwCharSet.Digits); break; | ||||
|                 case 'U': Add(PwCharSet.UpperCase, PwCharSet.Digits); break; | ||||
|                 case 'c': Add(PwCharSet.LowerConsonants); break; | ||||
|                 case 'C': | ||||
|                     Add(PwCharSet.LowerConsonants, | ||||
|                     PwCharSet.UpperConsonants); break; | ||||
|                 case 'z': Add(PwCharSet.UpperConsonants); break; | ||||
|                 case 'd': Add(PwCharSet.Digits); break; // Digit | ||||
|                 case 'h': Add(PwCharSet.LowerHex); break; | ||||
|                 case 'H': Add(PwCharSet.UpperHex); break; | ||||
|                 case 'l': Add(PwCharSet.LowerCase); break; | ||||
|                 case 'L': Add(PwCharSet.LowerCase, PwCharSet.UpperCase); break; | ||||
|                 case 'u': Add(PwCharSet.UpperCase); break; | ||||
|                 case 'p': Add(PwCharSet.Punctuation); break; | ||||
|                 case 'b': Add(PwCharSet.Brackets); break; | ||||
|                 case 's': Add(PwCharSet.PrintableAsciiSpecial); break; | ||||
|                 case 'S': | ||||
|                     Add(PwCharSet.UpperCase, PwCharSet.LowerCase); | ||||
|                     Add(PwCharSet.Digits, PwCharSet.PrintableAsciiSpecial); break; | ||||
|                 case 'v': Add(PwCharSet.LowerVowels); break; | ||||
|                 case 'V': Add(PwCharSet.LowerVowels, PwCharSet.UpperVowels); break; | ||||
|                 case 'Z': Add(PwCharSet.UpperVowels); break; | ||||
|                 case 'x': Add(PwCharSet.Latin1S); break; | ||||
|                 default: bResult = false; break; | ||||
|             } | ||||
|  | ||||
| 		public void AddRange(char chMin, char chMax) | ||||
| 		{ | ||||
| 			m_vChars.Capacity = m_vChars.Count + (chMax - chMin) + 1; | ||||
|             return bResult; | ||||
|         } | ||||
|  | ||||
| 			for(char ch = chMin; ch < chMax; ++ch) | ||||
| 				Add(ch); | ||||
|         public bool Remove(char ch) | ||||
|         { | ||||
|             m_vTab[ch / 8] &= (byte)(~(1 << (ch % 8))); | ||||
|             return m_lChars.Remove(ch); | ||||
|         } | ||||
|  | ||||
| 			Add(chMax); | ||||
| 		} | ||||
|         public bool Remove(string strCharacters) | ||||
|         { | ||||
|             Debug.Assert(strCharacters != null); | ||||
|             if (strCharacters == null) throw new ArgumentNullException("strCharacters"); | ||||
|  | ||||
| 		public bool AddCharSet(char chCharSetIdentifier) | ||||
| 		{ | ||||
| 			bool bResult = true; | ||||
|             bool bResult = true; | ||||
|             foreach (char ch in strCharacters) | ||||
|             { | ||||
|                 if (!Remove(ch)) bResult = false; | ||||
|             } | ||||
|  | ||||
| 			switch(chCharSetIdentifier) | ||||
| 			{ | ||||
| 				case 'a': Add(PwCharSet.LowerCase, PwCharSet.Digits); break; | ||||
| 				case 'A': Add(PwCharSet.LowerCase, PwCharSet.UpperCase, | ||||
| 					PwCharSet.Digits); break; | ||||
| 				case 'U': Add(PwCharSet.UpperCase, PwCharSet.Digits); break; | ||||
| 				case 'c': Add(PwCharSet.LowerConsonants); break; | ||||
| 				case 'C': Add(PwCharSet.LowerConsonants, | ||||
| 					PwCharSet.UpperConsonants); break; | ||||
| 				case 'z': Add(PwCharSet.UpperConsonants); break; | ||||
| 				case 'd': Add(PwCharSet.Digits); break; // Digit | ||||
| 				case 'h': Add(PwCharSet.LowerHex); break; | ||||
| 				case 'H': Add(PwCharSet.UpperHex); break; | ||||
| 				case 'l': Add(PwCharSet.LowerCase); break; | ||||
| 				case 'L': Add(PwCharSet.LowerCase, PwCharSet.UpperCase); break; | ||||
| 				case 'u': Add(PwCharSet.UpperCase); break; | ||||
| 				case 'p': Add(PwCharSet.Punctuation); break; | ||||
| 				case 'b': Add(PwCharSet.Brackets); break; | ||||
| 				case 's': Add(PwCharSet.PrintableAsciiSpecial); break; | ||||
| 				case 'S': Add(PwCharSet.UpperCase, PwCharSet.LowerCase); | ||||
| 					Add(PwCharSet.Digits, PwCharSet.PrintableAsciiSpecial); break; | ||||
| 				case 'v': Add(PwCharSet.LowerVowels); break; | ||||
| 				case 'V': Add(PwCharSet.LowerVowels, PwCharSet.UpperVowels); break; | ||||
| 				case 'Z': Add(PwCharSet.UpperVowels); break; | ||||
| 				case 'x': Add(m_strHighAnsi); break; | ||||
| 				default: bResult = false; break; | ||||
| 			} | ||||
|             return bResult; | ||||
|         } | ||||
|  | ||||
| 			return bResult; | ||||
| 		} | ||||
|         public bool RemoveIfAllExist(string strCharacters) | ||||
|         { | ||||
|             Debug.Assert(strCharacters != null); | ||||
|             if (strCharacters == null) throw new ArgumentNullException("strCharacters"); | ||||
|  | ||||
| 		public bool Remove(char ch) | ||||
| 		{ | ||||
| 			m_vTab[ch / 8] &= (byte)(~(1 << (ch % 8))); | ||||
| 			return m_vChars.Remove(ch); | ||||
| 		} | ||||
|             if (!Contains(strCharacters)) | ||||
|                 return false; | ||||
|  | ||||
| 		public bool Remove(string strCharacters) | ||||
| 		{ | ||||
| 			Debug.Assert(strCharacters != null); | ||||
| 			if(strCharacters == null) throw new ArgumentNullException("strCharacters"); | ||||
|             return Remove(strCharacters); | ||||
|         } | ||||
|  | ||||
| 			bool bResult = true; | ||||
| 			foreach(char ch in strCharacters) | ||||
| 			{ | ||||
| 				if(!Remove(ch)) bResult = false; | ||||
| 			} | ||||
|         /// <summary> | ||||
|         /// Convert the character set to a string containing all its characters. | ||||
|         /// </summary> | ||||
|         /// <returns>String containing all character set characters.</returns> | ||||
|         public override string ToString() | ||||
|         { | ||||
|             StringBuilder sb = new StringBuilder(m_lChars.Count); | ||||
|             foreach (char ch in m_lChars) | ||||
|                 sb.Append(ch); | ||||
|  | ||||
| 			return bResult; | ||||
| 		} | ||||
|             return sb.ToString(); | ||||
|         } | ||||
|  | ||||
| 		public bool RemoveIfAllExist(string strCharacters) | ||||
| 		{ | ||||
| 			Debug.Assert(strCharacters != null); | ||||
| 			if(strCharacters == null) throw new ArgumentNullException("strCharacters"); | ||||
|         public string PackAndRemoveCharRanges() | ||||
|         { | ||||
|             StringBuilder sb = new StringBuilder(); | ||||
|  | ||||
| 			if(!Contains(strCharacters)) | ||||
| 				return false; | ||||
|             sb.Append(RemoveIfAllExist(PwCharSet.UpperCase) ? 'U' : '_'); | ||||
|             sb.Append(RemoveIfAllExist(PwCharSet.LowerCase) ? 'L' : '_'); | ||||
|             sb.Append(RemoveIfAllExist(PwCharSet.Digits) ? 'D' : '_'); | ||||
|             sb.Append(RemoveIfAllExist(PwCharSet.Special) ? 'S' : '_'); | ||||
|             sb.Append(RemoveIfAllExist(PwCharSet.Punctuation) ? 'P' : '_'); | ||||
|             sb.Append(RemoveIfAllExist("-") ? 'm' : '_'); | ||||
|             sb.Append(RemoveIfAllExist("_") ? 'u' : '_'); | ||||
|             sb.Append(RemoveIfAllExist(" ") ? 's' : '_'); | ||||
|             sb.Append(RemoveIfAllExist(PwCharSet.Brackets) ? 'B' : '_'); | ||||
|             sb.Append(RemoveIfAllExist(PwCharSet.Latin1S) ? 'H' : '_'); | ||||
|  | ||||
| 			return Remove(strCharacters); | ||||
| 		} | ||||
|             return sb.ToString(); | ||||
|         } | ||||
|  | ||||
| 		/// <summary> | ||||
| 		/// Convert the character set to a string containing all its characters. | ||||
| 		/// </summary> | ||||
| 		/// <returns>String containing all character set characters.</returns> | ||||
| 		public override string ToString() | ||||
| 		{ | ||||
| 			StringBuilder sb = new StringBuilder(); | ||||
| 			foreach(char ch in m_vChars) | ||||
| 				sb.Append(ch); | ||||
|         public void UnpackCharRanges(string strRanges) | ||||
|         { | ||||
|             if (strRanges == null) { Debug.Assert(false); return; } | ||||
|             if (strRanges.Length < 10) { Debug.Assert(false); return; } | ||||
|  | ||||
| 			return sb.ToString(); | ||||
| 		} | ||||
|  | ||||
| 		public string PackAndRemoveCharRanges() | ||||
| 		{ | ||||
| 			StringBuilder sb = new StringBuilder(); | ||||
|  | ||||
| 			sb.Append(RemoveIfAllExist(PwCharSet.UpperCase) ? 'U' : '_'); | ||||
| 			sb.Append(RemoveIfAllExist(PwCharSet.LowerCase) ? 'L' : '_'); | ||||
| 			sb.Append(RemoveIfAllExist(PwCharSet.Digits) ? 'D' : '_'); | ||||
| 			sb.Append(RemoveIfAllExist(m_strSpecial) ? 'S' : '_'); | ||||
| 			sb.Append(RemoveIfAllExist(PwCharSet.Punctuation) ? 'P' : '_'); | ||||
| 			sb.Append(RemoveIfAllExist(@"-") ? 'm' : '_'); | ||||
| 			sb.Append(RemoveIfAllExist(@"_") ? 'u' : '_'); | ||||
| 			sb.Append(RemoveIfAllExist(@" ") ? 's' : '_'); | ||||
| 			sb.Append(RemoveIfAllExist(PwCharSet.Brackets) ? 'B' : '_'); | ||||
| 			sb.Append(RemoveIfAllExist(m_strHighAnsi) ? 'H' : '_'); | ||||
|  | ||||
| 			return sb.ToString(); | ||||
| 		} | ||||
|  | ||||
| 		public void UnpackCharRanges(string strRanges) | ||||
| 		{ | ||||
| 			if(strRanges == null) { Debug.Assert(false); return; } | ||||
| 			if(strRanges.Length < 10) { Debug.Assert(false); return; } | ||||
|  | ||||
| 			if(strRanges[0] != '_') Add(PwCharSet.UpperCase); | ||||
| 			if(strRanges[1] != '_') Add(PwCharSet.LowerCase); | ||||
| 			if(strRanges[2] != '_') Add(PwCharSet.Digits); | ||||
| 			if(strRanges[3] != '_') Add(m_strSpecial); | ||||
| 			if(strRanges[4] != '_') Add(PwCharSet.Punctuation); | ||||
| 			if(strRanges[5] != '_') Add('-'); | ||||
| 			if(strRanges[6] != '_') Add('_'); | ||||
| 			if(strRanges[7] != '_') Add(' '); | ||||
| 			if(strRanges[8] != '_') Add(PwCharSet.Brackets); | ||||
| 			if(strRanges[9] != '_') Add(m_strHighAnsi); | ||||
| 		} | ||||
| 	} | ||||
|             if (strRanges[0] != '_') Add(PwCharSet.UpperCase); | ||||
|             if (strRanges[1] != '_') Add(PwCharSet.LowerCase); | ||||
|             if (strRanges[2] != '_') Add(PwCharSet.Digits); | ||||
|             if (strRanges[3] != '_') Add(PwCharSet.Special); | ||||
|             if (strRanges[4] != '_') Add(PwCharSet.Punctuation); | ||||
|             if (strRanges[5] != '_') Add('-'); | ||||
|             if (strRanges[6] != '_') Add('_'); | ||||
|             if (strRanges[7] != '_') Add(' '); | ||||
|             if (strRanges[8] != '_') Add(PwCharSet.Brackets); | ||||
|             if (strRanges[9] != '_') Add(PwCharSet.Latin1S); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| /* | ||||
|   KeePass Password Safe - The Open-Source Password Manager | ||||
|   Copyright (C) 2003-2016 Dominik Reichl <dominik.reichl@t-online.de> | ||||
|   Copyright (C) 2003-2025 Dominik Reichl <dominik.reichl@t-online.de> | ||||
|  | ||||
|   This program is free software; you can redistribute it and/or modify | ||||
|   it under the terms of the GNU General Public License as published by | ||||
| @@ -20,133 +20,172 @@ | ||||
| using System; | ||||
| using System.Collections.Generic; | ||||
| using System.Diagnostics; | ||||
| using System.Security.Cryptography; | ||||
| using System.Text; | ||||
|  | ||||
| #if !KeePassUAP | ||||
| using System.Security.Cryptography; | ||||
| #endif | ||||
|  | ||||
| using KeePassLib.Resources; | ||||
| using KeePassLib.Security; | ||||
| using KeePassLib.Utility; | ||||
|  | ||||
| namespace KeePassLib.Cryptography.PasswordGenerator | ||||
| { | ||||
| 	public enum PwgError | ||||
| 	{ | ||||
| 		Success = 0, | ||||
| 		Unknown = 1, | ||||
| 		TooFewCharacters = 2, | ||||
| 		UnknownAlgorithm = 3 | ||||
| 	} | ||||
|     public enum PwgError | ||||
|     { | ||||
|         Success = 0, | ||||
|         Unknown = 1, | ||||
|         TooFewCharacters = 2, | ||||
|         UnknownAlgorithm = 3, | ||||
|         InvalidCharSet = 4, | ||||
|         InvalidPattern = 5 | ||||
|     } | ||||
|  | ||||
| 	/// <summary> | ||||
| 	/// Utility functions for generating random passwords. | ||||
| 	/// </summary> | ||||
| 	public static class PwGenerator | ||||
| 	{ | ||||
| 		public static PwgError Generate(out ProtectedString psOut, | ||||
| 			PwProfile pwProfile, byte[] pbUserEntropy, | ||||
| 			CustomPwGeneratorPool pwAlgorithmPool) | ||||
| 		{ | ||||
| 			Debug.Assert(pwProfile != null); | ||||
| 			if (pwProfile == null) throw new ArgumentNullException("pwProfile"); | ||||
|     /// <summary> | ||||
|     /// Password generator. | ||||
|     /// </summary> | ||||
|     public static class PwGenerator | ||||
|     { | ||||
|  | ||||
| 			CryptoRandomStream crs = CreateCryptoStream(pbUserEntropy); | ||||
| 			PwgError e = PwgError.Unknown; | ||||
|         private static CryptoRandomStream CreateRandomStream(byte[] pbAdditionalEntropy, | ||||
|             out byte[] pbKey) | ||||
|         { | ||||
|             pbKey = CryptoRandom.Instance.GetRandomBytes(128); | ||||
|  | ||||
| 			if (pwProfile.GeneratorType == PasswordGeneratorType.CharSet) | ||||
| 				e = CharSetBasedGenerator.Generate(out psOut, pwProfile, crs); | ||||
| 			else if (pwProfile.GeneratorType == PasswordGeneratorType.Pattern) | ||||
| 				e = PatternBasedGenerator.Generate(out psOut, pwProfile, crs); | ||||
| 			else if (pwProfile.GeneratorType == PasswordGeneratorType.Custom) | ||||
| 				e = GenerateCustom(out psOut, pwProfile, crs, pwAlgorithmPool); | ||||
| 			else { Debug.Assert(false); psOut = ProtectedString.Empty; } | ||||
|             // Mix in additional entropy | ||||
|             Debug.Assert(pbKey.Length >= 64); | ||||
|             if ((pbAdditionalEntropy != null) && (pbAdditionalEntropy.Length != 0)) | ||||
|             { | ||||
|                 using (SHA512Managed h = new SHA512Managed()) | ||||
|                 { | ||||
|                     byte[] pbHash = h.ComputeHash(pbAdditionalEntropy); | ||||
|                     MemUtil.XorArray(pbHash, 0, pbKey, 0, pbHash.Length); | ||||
|                     MemUtil.ZeroByteArray(pbHash); | ||||
|                 } | ||||
|             } | ||||
|  | ||||
| 			return e; | ||||
| 		} | ||||
|             return new CryptoRandomStream(CrsAlgorithm.ChaCha20, pbKey); | ||||
|         } | ||||
|  | ||||
| 		private static CryptoRandomStream CreateCryptoStream(byte[] pbAdditionalEntropy) | ||||
| 		{ | ||||
| 			byte[] pbKey = CryptoRandom.Instance.GetRandomBytes(128); | ||||
|         internal static char GenerateCharacter(PwCharSet pwCharSet, | ||||
|             CryptoRandomStream crsRandomSource) | ||||
|         { | ||||
|             uint cc = pwCharSet.Size; | ||||
|             if (cc == 0) return char.MinValue; | ||||
|  | ||||
| 			// Mix in additional entropy | ||||
| 			Debug.Assert(pbKey.Length >= 64); | ||||
| 			if ((pbAdditionalEntropy != null) && (pbAdditionalEntropy.Length > 0)) | ||||
| 			{ | ||||
| 				using (SHA512Managed h = new SHA512Managed()) | ||||
| 				{ | ||||
| 					byte[] pbHash = h.ComputeHash(pbAdditionalEntropy); | ||||
| 					MemUtil.XorArray(pbHash, 0, pbKey, 0, pbHash.Length); | ||||
| 				} | ||||
| 			} | ||||
|             uint i = (uint)crsRandomSource.GetRandomUInt64(cc); | ||||
|             return pwCharSet[i]; | ||||
|         } | ||||
|  | ||||
| 			return new CryptoRandomStream(CrsAlgorithm.ChaCha20, pbKey); | ||||
| 		} | ||||
|         internal static bool PrepareCharSet(PwCharSet pwCharSet, PwProfile pwProfile) | ||||
|         { | ||||
|             uint cc = pwCharSet.Size; | ||||
|             for (uint i = 0; i < cc; ++i) | ||||
|             { | ||||
|                 char ch = pwCharSet[i]; | ||||
|                 if ((ch == char.MinValue) || (ch == '\t') || (ch == '\r') || | ||||
|                     (ch == '\n') || char.IsSurrogate(ch)) | ||||
|                     return false; | ||||
|             } | ||||
|  | ||||
| 		internal static char GenerateCharacter(PwProfile pwProfile, | ||||
| 			PwCharSet pwCharSet, CryptoRandomStream crsRandomSource) | ||||
| 		{ | ||||
| 			if (pwCharSet.Size == 0) return char.MinValue; | ||||
|             if (pwProfile.ExcludeLookAlike) pwCharSet.Remove(PwCharSet.LookAlike); | ||||
|  | ||||
| 			ulong uIndex = crsRandomSource.GetRandomUInt64(); | ||||
| 			uIndex %= (ulong)pwCharSet.Size; | ||||
|             if (!string.IsNullOrEmpty(pwProfile.ExcludeCharacters)) | ||||
|                 pwCharSet.Remove(pwProfile.ExcludeCharacters); | ||||
|  | ||||
| 			char ch = pwCharSet[(uint)uIndex]; | ||||
|             return true; | ||||
|         } | ||||
|  | ||||
| 			if (pwProfile.NoRepeatingCharacters) | ||||
| 				pwCharSet.Remove(ch); | ||||
|         internal static void Shuffle(char[] v, CryptoRandomStream crsRandomSource) | ||||
|         { | ||||
|             if (v == null) { Debug.Assert(false); return; } | ||||
|             if (crsRandomSource == null) { Debug.Assert(false); return; } | ||||
|  | ||||
| 			return ch; | ||||
| 		} | ||||
|             for (int i = v.Length - 1; i >= 1; --i) | ||||
|             { | ||||
|                 int j = (int)crsRandomSource.GetRandomUInt64((ulong)(i + 1)); | ||||
|  | ||||
| 		internal static void PrepareCharSet(PwCharSet pwCharSet, PwProfile pwProfile) | ||||
| 		{ | ||||
| 			pwCharSet.Remove(PwCharSet.Invalid); | ||||
|                 char t = v[i]; | ||||
|                 v[i] = v[j]; | ||||
|                 v[j] = t; | ||||
|             } | ||||
|         } | ||||
|  | ||||
| 			if (pwProfile.ExcludeLookAlike) pwCharSet.Remove(PwCharSet.LookAlike); | ||||
|         private static PwgError GenerateCustom(out ProtectedString psOut, | ||||
|             PwProfile pwProfile, CryptoRandomStream crs, | ||||
|             CustomPwGeneratorPool pwAlgorithmPool) | ||||
|         { | ||||
|             psOut = ProtectedString.Empty; | ||||
|  | ||||
| 			if (pwProfile.ExcludeCharacters.Length > 0) | ||||
| 				pwCharSet.Remove(pwProfile.ExcludeCharacters); | ||||
| 		} | ||||
|             Debug.Assert(pwProfile.GeneratorType == PasswordGeneratorType.Custom); | ||||
|             if (pwAlgorithmPool == null) return PwgError.UnknownAlgorithm; | ||||
|  | ||||
| 		internal static void ShufflePassword(char[] pPassword, | ||||
| 			CryptoRandomStream crsRandomSource) | ||||
| 		{ | ||||
| 			Debug.Assert(pPassword != null); if (pPassword == null) return; | ||||
| 			Debug.Assert(crsRandomSource != null); if (crsRandomSource == null) return; | ||||
|             string strID = pwProfile.CustomAlgorithmUuid; | ||||
|             if (string.IsNullOrEmpty(strID)) return PwgError.UnknownAlgorithm; | ||||
|  | ||||
| 			if (pPassword.Length <= 1) return; // Nothing to shuffle | ||||
|             byte[] pbUuid = Convert.FromBase64String(strID); | ||||
|             PwUuid uuid = new PwUuid(pbUuid); | ||||
|             CustomPwGenerator pwg = pwAlgorithmPool.Find(uuid); | ||||
|             if (pwg == null) { Debug.Assert(false); return PwgError.UnknownAlgorithm; } | ||||
|  | ||||
| 			for (int nSelect = 0; nSelect < pPassword.Length; ++nSelect) | ||||
| 			{ | ||||
| 				ulong uRandomIndex = crsRandomSource.GetRandomUInt64(); | ||||
| 				uRandomIndex %= (ulong)(pPassword.Length - nSelect); | ||||
|             ProtectedString pwd = pwg.Generate(pwProfile.CloneDeep(), crs); | ||||
|             if (pwd == null) return PwgError.Unknown; | ||||
|  | ||||
| 				char chTemp = pPassword[nSelect]; | ||||
| 				pPassword[nSelect] = pPassword[nSelect + (int)uRandomIndex]; | ||||
| 				pPassword[nSelect + (int)uRandomIndex] = chTemp; | ||||
| 			} | ||||
| 		} | ||||
|             psOut = pwd; | ||||
|             return PwgError.Success; | ||||
|         } | ||||
|  | ||||
| 		private static PwgError GenerateCustom(out ProtectedString psOut, | ||||
| 			PwProfile pwProfile, CryptoRandomStream crs, | ||||
| 			CustomPwGeneratorPool pwAlgorithmPool) | ||||
| 		{ | ||||
| 			psOut = ProtectedString.Empty; | ||||
|         internal static string ErrorToString(PwgError e, bool bHeader) | ||||
|         { | ||||
|             if (e == PwgError.Success) { Debug.Assert(false); return string.Empty; } | ||||
|             if ((e == PwgError.Unknown) && bHeader) return KLRes.PwGenFailed; | ||||
|  | ||||
| 			Debug.Assert(pwProfile.GeneratorType == PasswordGeneratorType.Custom); | ||||
| 			if (pwAlgorithmPool == null) return PwgError.UnknownAlgorithm; | ||||
|             string str = KLRes.UnknownError; | ||||
|             switch (e) | ||||
|             { | ||||
|                 // case PwgError.Success: | ||||
|                 //	break; | ||||
|  | ||||
| 			string strID = pwProfile.CustomAlgorithmUuid; | ||||
| 			if (string.IsNullOrEmpty(strID)) { Debug.Assert(false); return PwgError.UnknownAlgorithm; } | ||||
|                 case PwgError.Unknown: | ||||
|                     break; | ||||
|  | ||||
| 			byte[] pbUuid = Convert.FromBase64String(strID); | ||||
| 			PwUuid uuid = new PwUuid(pbUuid); | ||||
| 			CustomPwGenerator pwg = pwAlgorithmPool.Find(uuid); | ||||
| 			if (pwg == null) { Debug.Assert(false); return PwgError.UnknownAlgorithm; } | ||||
|                 case PwgError.TooFewCharacters: | ||||
|                     str = KLRes.CharSetTooFewChars; | ||||
|                     break; | ||||
|  | ||||
| 			ProtectedString pwd = pwg.Generate(pwProfile.CloneDeep(), crs); | ||||
| 			if (pwd == null) return PwgError.Unknown; | ||||
|                 case PwgError.UnknownAlgorithm: | ||||
|                     str = KLRes.AlgorithmUnknown; | ||||
|                     break; | ||||
|  | ||||
| 			psOut = pwd; | ||||
| 			return PwgError.Success; | ||||
| 		} | ||||
| 	} | ||||
|                 case PwgError.InvalidCharSet: | ||||
|                     str = KLRes.CharSetInvalid; | ||||
|                     break; | ||||
|  | ||||
|                 case PwgError.InvalidPattern: | ||||
|                     str = KLRes.PatternInvalid; | ||||
|                     break; | ||||
|  | ||||
|                 default: | ||||
|                     Debug.Assert(false); | ||||
|                     break; | ||||
|             } | ||||
|  | ||||
|             if (bHeader) | ||||
|                 str = KLRes.PwGenFailed + MessageService.NewParagraph + str; | ||||
|  | ||||
|             return str; | ||||
|         } | ||||
|  | ||||
|         internal static string ErrorToString(Exception ex, bool bHeader) | ||||
|         { | ||||
|             string str = ((ex == null) ? KLRes.UnknownError : | ||||
|                 StrUtil.FormatException(ex)); | ||||
|  | ||||
|             if (bHeader) | ||||
|                 str = KLRes.PwGenFailed + MessageService.NewParagraph + str; | ||||
|  | ||||
|             return str; | ||||
|         } | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| /* | ||||
|   KeePass Password Safe - The Open-Source Password Manager | ||||
|   Copyright (C) 2003-2017 Dominik Reichl <dominik.reichl@t-online.de> | ||||
|   Copyright (C) 2003-2025 Dominik Reichl <dominik.reichl@t-online.de> | ||||
|  | ||||
|   This program is free software; you can redistribute it and/or modify | ||||
|   it under the terms of the GNU General Public License as published by | ||||
| @@ -19,114 +19,115 @@ | ||||
|  | ||||
| using System; | ||||
| using System.Collections.Generic; | ||||
| using System.Text; | ||||
| using System.Diagnostics; | ||||
| using System.Text; | ||||
|  | ||||
| using KeePassLib.Utility; | ||||
|  | ||||
| namespace KeePassLib.Cryptography | ||||
| { | ||||
| 	public static class PopularPasswords | ||||
| 	{ | ||||
| 		private static Dictionary<int, Dictionary<string, bool>> m_dicts = | ||||
| 			new Dictionary<int, Dictionary<string, bool>>(); | ||||
|     public static class PopularPasswords | ||||
|     { | ||||
|         private static readonly Dictionary<int, Dictionary<char[], bool>> g_dicts = | ||||
|             new Dictionary<int, Dictionary<char[], bool>>(); | ||||
|  | ||||
| 		internal static int MaxLength | ||||
| 		{ | ||||
| 			get | ||||
| 			{ | ||||
| 				int iMaxLen = 0; | ||||
| 				foreach(int iLen in m_dicts.Keys) | ||||
| 				{ | ||||
| 					if(iLen > iMaxLen) iMaxLen = iLen; | ||||
| 				} | ||||
|         internal static int MaxLength | ||||
|         { | ||||
|             get | ||||
|             { | ||||
|                 Debug.Assert(g_dicts.Count > 0); // Should be initialized | ||||
|  | ||||
| 				return iMaxLen; | ||||
| 			} | ||||
| 		} | ||||
|                 int iMaxLen = 0; | ||||
|                 foreach (int iLen in g_dicts.Keys) | ||||
|                 { | ||||
|                     if (iLen > iMaxLen) iMaxLen = iLen; | ||||
|                 } | ||||
|  | ||||
| 		internal static bool ContainsLength(int nLength) | ||||
| 		{ | ||||
| 			Dictionary<string, bool> dDummy; | ||||
| 			return m_dicts.TryGetValue(nLength, out dDummy); | ||||
| 		} | ||||
|                 return iMaxLen; | ||||
|             } | ||||
|         } | ||||
|  | ||||
| 		public static bool IsPopularPassword(char[] vPassword) | ||||
| 		{ | ||||
| 			ulong uDummy; | ||||
| 			return IsPopularPassword(vPassword, out uDummy); | ||||
| 		} | ||||
|         internal static bool ContainsLength(int nLength) | ||||
|         { | ||||
|             Dictionary<char[], bool> dDummy; | ||||
|             return g_dicts.TryGetValue(nLength, out dDummy); | ||||
|         } | ||||
|  | ||||
| 		public static bool IsPopularPassword(char[] vPassword, out ulong uDictSize) | ||||
| 		{ | ||||
| 			if(vPassword == null) throw new ArgumentNullException("vPassword"); | ||||
| 			if(vPassword.Length == 0) { uDictSize = 0; return false; } | ||||
|         public static bool IsPopularPassword(char[] vPassword) | ||||
|         { | ||||
|             ulong uDummy; | ||||
|             return IsPopularPassword(vPassword, out uDummy); | ||||
|         } | ||||
|  | ||||
| 			string str = new string(vPassword); | ||||
|         public static bool IsPopularPassword(char[] vPassword, out ulong uDictSize) | ||||
|         { | ||||
|             if (vPassword == null) throw new ArgumentNullException("vPassword"); | ||||
|             if (vPassword.Length == 0) { uDictSize = 0; return false; } | ||||
|  | ||||
| 			try { return IsPopularPasswordPriv(str, out uDictSize); } | ||||
| 			catch(Exception) { Debug.Assert(false); } | ||||
| #if DEBUG | ||||
|             Array.ForEach(vPassword, ch => Debug.Assert(ch == char.ToLower(ch))); | ||||
| #endif | ||||
|  | ||||
| 			uDictSize = 0; | ||||
| 			return false; | ||||
| 		} | ||||
|             try { return IsPopularPasswordPriv(vPassword, out uDictSize); } | ||||
|             catch (Exception) { Debug.Assert(false); } | ||||
|  | ||||
| 		private static bool IsPopularPasswordPriv(string str, out ulong uDictSize) | ||||
| 		{ | ||||
| 			Debug.Assert(m_dicts.Count > 0); // Should be initialized with data | ||||
|             uDictSize = 0; | ||||
|             return false; | ||||
|         } | ||||
|  | ||||
| 			Dictionary<string, bool> d; | ||||
| 			if(!m_dicts.TryGetValue(str.Length, out d)) | ||||
| 			{ | ||||
| 				uDictSize = 0; | ||||
| 				return false; | ||||
| 			} | ||||
|         private static bool IsPopularPasswordPriv(char[] vPassword, out ulong uDictSize) | ||||
|         { | ||||
|             Debug.Assert(g_dicts.Count > 0); // Should be initialized with data | ||||
|  | ||||
| 			uDictSize = (ulong)d.Count; | ||||
| 			return d.ContainsKey(str); | ||||
| 		} | ||||
|             Dictionary<char[], bool> d; | ||||
|             if (!g_dicts.TryGetValue(vPassword.Length, out d)) | ||||
|             { | ||||
|                 uDictSize = 0; | ||||
|                 return false; | ||||
|             } | ||||
|  | ||||
| 		public static void Add(byte[] pbData, bool bGZipped) | ||||
| 		{ | ||||
| 			try | ||||
| 			{ | ||||
| 				if(bGZipped) | ||||
| 					pbData = MemUtil.Decompress(pbData); | ||||
|             uDictSize = (ulong)d.Count; | ||||
|             return d.ContainsKey(vPassword); | ||||
|         } | ||||
|  | ||||
| 				string strData = StrUtil.Utf8.GetString(pbData, 0, pbData.Length); | ||||
| 				if(string.IsNullOrEmpty(strData)) { Debug.Assert(false); return; } | ||||
|         public static void Add(byte[] pbData, bool bGZipped) | ||||
|         { | ||||
|             try | ||||
|             { | ||||
|                 if (bGZipped) | ||||
|                     pbData = MemUtil.Decompress(pbData); | ||||
|  | ||||
| 				if(!char.IsWhiteSpace(strData[strData.Length - 1])) | ||||
| 					strData += "\n"; | ||||
|                 string strData = StrUtil.Utf8.GetString(pbData, 0, pbData.Length); | ||||
|                 if (string.IsNullOrEmpty(strData)) { Debug.Assert(false); return; } | ||||
|  | ||||
| 				StringBuilder sb = new StringBuilder(); | ||||
| 				for(int i = 0; i < strData.Length; ++i) | ||||
| 				{ | ||||
| 					char ch = strData[i]; | ||||
|                 StringBuilder sb = new StringBuilder(); | ||||
|                 for (int i = 0; i <= strData.Length; ++i) | ||||
|                 { | ||||
|                     char ch = ((i == strData.Length) ? ' ' : strData[i]); | ||||
|  | ||||
| 					if(char.IsWhiteSpace(ch)) | ||||
| 					{ | ||||
| 						int cc = sb.Length; | ||||
| 						if(cc > 0) | ||||
| 						{ | ||||
| 							string strWord = sb.ToString(); | ||||
| 							Debug.Assert(strWord.Length == cc); | ||||
|                     if (char.IsWhiteSpace(ch)) | ||||
|                     { | ||||
|                         int cc = sb.Length; | ||||
|                         if (cc > 0) | ||||
|                         { | ||||
|                             char[] vWord = new char[cc]; | ||||
|                             sb.CopyTo(0, vWord, 0, cc); | ||||
|  | ||||
| 							Dictionary<string, bool> d; | ||||
| 							if(!m_dicts.TryGetValue(cc, out d)) | ||||
| 							{ | ||||
| 								d = new Dictionary<string, bool>(); | ||||
| 								m_dicts[cc] = d; | ||||
| 							} | ||||
|                             Dictionary<char[], bool> d; | ||||
|                             if (!g_dicts.TryGetValue(cc, out d)) | ||||
|                             { | ||||
|                                 d = new Dictionary<char[], bool>(MemUtil.ArrayHelperExOfChar); | ||||
|                                 g_dicts[cc] = d; | ||||
|                             } | ||||
|  | ||||
| 							d[strWord] = true; | ||||
| 							sb.Remove(0, cc); | ||||
| 						} | ||||
| 					} | ||||
| 					else sb.Append(char.ToLower(ch)); | ||||
| 				} | ||||
| 			} | ||||
| 			catch(Exception) { Debug.Assert(false); } | ||||
| 		} | ||||
| 	} | ||||
|                             d[vWord] = true; | ||||
|                             sb.Remove(0, cc); | ||||
|                         } | ||||
|                     } | ||||
|                     else sb.Append(char.ToLower(ch)); | ||||
|                 } | ||||
|             } | ||||
|             catch (Exception) { Debug.Assert(false); } | ||||
|         } | ||||
|     } | ||||
| } | ||||
|   | ||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -46,4 +46,12 @@ namespace KeePassLib.Delegates | ||||
| 	public delegate void VoidDelegate(); | ||||
|  | ||||
| 	public delegate string StrPwEntryDelegate(string str, PwEntry pe); | ||||
|  | ||||
|     public delegate TResult GFunc<TResult>(); | ||||
|     public delegate TResult GFunc<T, TResult>(T o); | ||||
|     public delegate TResult GFunc<T1, T2, TResult>(T1 o1, T2 o2); | ||||
|     public delegate TResult GFunc<T1, T2, T3, TResult>(T1 o1, T2 o2, T3 o3); | ||||
|     public delegate TResult GFunc<T1, T2, T3, T4, TResult>(T1 o1, T2 o2, T3 o3, T4 o4); | ||||
|     public delegate TResult GFunc<T1, T2, T3, T4, T5, TResult>(T1 o1, T2 o2, T3 o3, T4 o4, T5 o5); | ||||
|     public delegate TResult GFunc<T1, T2, T3, T4, T5, T6, TResult>(T1 o1, T2 o2, T3 o3, T4 o4, T5 o5, T6 o6); | ||||
| } | ||||
|   | ||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										10183
									
								
								src/keepass2android-app/Assets/MostPopularPasswords.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10183
									
								
								src/keepass2android-app/Assets/MostPopularPasswords.txt
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -1546,10 +1546,10 @@ namespace keepass2android | ||||
|             string url = _stringViews[urlFieldKey].Text; | ||||
| 			if (url == null) return false; | ||||
|  | ||||
| 			// Default http:// if no protocol specified | ||||
| 			// Default https:// if no protocol specified | ||||
| 			if ((!url.Contains(":") || (url.StartsWith("www.")))) | ||||
| 			{ | ||||
| 				url = "http://" + url; | ||||
| 				url = "https://" + url; | ||||
| 			} | ||||
|  | ||||
| 			try | ||||
|   | ||||
| @@ -23,6 +23,7 @@ using Android.App; | ||||
| using Android.App.Admin; | ||||
| using Android.Content; | ||||
| using Android.Content.PM; | ||||
| using Android.Content.Res; | ||||
| using Android.Graphics; | ||||
| using Android.OS; | ||||
| using Android.Preferences; | ||||
| @@ -66,10 +67,15 @@ namespace keepass2android | ||||
|             Resource.Id.cb_exclude_lookalike | ||||
|         }; | ||||
|  | ||||
|          | ||||
|  | ||||
| 		PasswordFont _passwordFont = new PasswordFont(); | ||||
|  | ||||
|         private static object _popularPasswordsLock = new object(); | ||||
|         private static bool _popularPasswordsInitialized = false; | ||||
|  | ||||
| 		private ActivityDesign _design; | ||||
|  | ||||
|         private ActivityDesign _design; | ||||
| 	    public GeneratePasswordActivity() | ||||
| 	    { | ||||
| 		    _design = new ActivityDesign(this); | ||||
| @@ -302,6 +308,10 @@ namespace keepass2android | ||||
|  | ||||
|  | ||||
|             EditText txtPasswordToSet = (EditText) FindViewById(Resource.Id.password_edit); | ||||
|             txtPasswordToSet.TextChanged += (sender, args) => | ||||
|             { | ||||
|                 Task.Run(() => UpdatePasswordStrengthEstimate(txtPasswordToSet.Text)); | ||||
|             }; | ||||
|  | ||||
|             _passwordFont.ApplyTo(txtPasswordToSet); | ||||
|  | ||||
| @@ -467,51 +477,76 @@ namespace keepass2android | ||||
|                 return; | ||||
|  | ||||
|             String password = ""; | ||||
|             uint passwordBits = 0; | ||||
|              | ||||
|              | ||||
|             Task.Run(() => | ||||
|             { | ||||
|                 password = GeneratePassword(); | ||||
|                 passwordBits = QualityEstimation.EstimatePasswordBits(password.ToCharArray()); | ||||
|                 RunOnUiThread(() => | ||||
|                 { | ||||
|                     EditText txtPassword = (EditText)FindViewById(Resource.Id.password_edit); | ||||
|                     txtPassword.Text = password; | ||||
|  | ||||
|                     var progressBar = FindViewById<ProgressBar>(Resource.Id.pb_password_strength); | ||||
|  | ||||
|                     progressBar.Progress = (int)passwordBits; | ||||
|                     progressBar.Max = 128; | ||||
|  | ||||
|                     Color color = new Color(196, 63, 49); | ||||
|                     if (passwordBits > 40) | ||||
|                     { | ||||
|                         color = new Color(219, 152, 55); | ||||
|                     } | ||||
|  | ||||
|                     if (passwordBits > 64) | ||||
|                     { | ||||
|                         color = new Color(96, 138, 38); | ||||
|                     } | ||||
|  | ||||
|                     if (passwordBits > 100) | ||||
|                     { | ||||
|                         color = new Color(31, 128, 31); | ||||
|                     } | ||||
|  | ||||
|                     progressBar.ProgressDrawable.SetColorFilter(new PorterDuffColorFilter(color, | ||||
|                         PorterDuff.Mode.SrcIn)); | ||||
|  | ||||
|                     FindViewById<TextView>(Resource.Id.tv_password_strength).Text = " " + passwordBits + " bits"; | ||||
|  | ||||
|  | ||||
|  | ||||
|                     UpdateProfileSpinnerSelection(); | ||||
|                 }); | ||||
|             }); | ||||
|              | ||||
|         } | ||||
|  | ||||
|         private void UpdatePasswordStrengthEstimate(string password) | ||||
|         { | ||||
|             lock (_popularPasswordsLock) | ||||
|             { | ||||
|                 if (!_popularPasswordsInitialized) | ||||
|                 { | ||||
|  | ||||
|                     using (StreamReader sr = new StreamReader(Assets.Open("MostPopularPasswords.txt"))) | ||||
|                     { | ||||
|                         var bytes = default(byte[]); | ||||
|                         using (var memstream = new MemoryStream()) | ||||
|                         { | ||||
|                             sr.BaseStream.CopyTo(memstream); | ||||
|                             bytes = memstream.ToArray(); | ||||
|                         } | ||||
|                         PopularPasswords.Add(bytes, false); | ||||
|                     } | ||||
|                 } | ||||
|                      | ||||
|             } | ||||
|             uint passwordBits = QualityEstimation.EstimatePasswordBits(password.ToCharArray()); | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
|             RunOnUiThread(() => | ||||
|             { | ||||
|                 var progressBar = FindViewById<ProgressBar>(Resource.Id.pb_password_strength); | ||||
|  | ||||
|                 progressBar.Progress = (int)passwordBits; | ||||
|                 progressBar.Max = 128; | ||||
|  | ||||
|                 Color color = new Color(196, 63, 49); | ||||
|                 if (passwordBits > 40) | ||||
|                 { | ||||
|                     color = new Color(219, 152, 55); | ||||
|                 } | ||||
|  | ||||
|                 if (passwordBits > 64) | ||||
|                 { | ||||
|                     color = new Color(96, 138, 38); | ||||
|                 } | ||||
|  | ||||
|                 if (passwordBits > 100) | ||||
|                 { | ||||
|                     color = new Color(31, 128, 31); | ||||
|                 } | ||||
|  | ||||
|                 progressBar.ProgressDrawable.SetColorFilter(new PorterDuffColorFilter(color, | ||||
|                     PorterDuff.Mode.SrcIn)); | ||||
|  | ||||
|                 FindViewById<TextView>(Resource.Id.tv_password_strength).Text = " " + passwordBits + " bits"; | ||||
|             }); | ||||
|         } | ||||
|  | ||||
|         private void UpdateProfileSpinnerSelection() | ||||
|         { | ||||
|             int? lastUsedIndex = _profiles.TryFindLastUsedProfileIndex(); | ||||
|   | ||||
| @@ -1423,6 +1423,8 @@ namespace keepass2android | ||||
| 			if (cbQuickUnlock == null) | ||||
| 				throw new NullPointerException("cpQuickUnlock"); | ||||
| 			App.Kp2a.SetQuickUnlockEnabled(cbQuickUnlock.Checked); | ||||
|             App.Kp2a.ScreenLockWasEnabledWhenOpeningDatabase = | ||||
|                 (((KeyguardManager)GetSystemService(Context.KeyguardService)!)!).IsDeviceSecure; | ||||
|  | ||||
|             if ((_loadDbFileTask != null) &&  (App.Kp2a.OfflineMode != _loadDbTaskOffline)) | ||||
| 			{ | ||||
|   | ||||
| @@ -25,6 +25,7 @@ using Android.Widget; | ||||
| using Android.Content.PM; | ||||
| using KeePassLib.Keys; | ||||
| using Android.Preferences; | ||||
| using Android.Provider; | ||||
| using Android.Runtime; | ||||
|  | ||||
| using Android.Views.InputMethods; | ||||
| @@ -162,6 +163,29 @@ namespace keepass2android | ||||
|             if (bundle != null) | ||||
|                 numFailedAttempts = bundle.GetInt(NumFailedAttemptsKey, 0); | ||||
|  | ||||
|             FindViewById(Resource.Id.QuickUnlock_buttonEnableLock).Click += (object sender, EventArgs e) => | ||||
|             { | ||||
| 				Intent intent = new Intent(Settings.ActionSecuritySettings); | ||||
|                 StartActivity(intent); | ||||
|  | ||||
|             }; | ||||
|  | ||||
|             FindViewById(Resource.Id.QuickUnlock_buttonCloseDb).Click += (object sender, EventArgs e) => | ||||
|             { | ||||
|                 App.Kp2a.Lock(false); | ||||
|             }; | ||||
|  | ||||
|             if (App.Kp2a.ScreenLockWasEnabledWhenOpeningDatabase == false) | ||||
|             { | ||||
| 				FindViewById(Resource.Id.QuickUnlockForm).Visibility = ViewStates.Gone; | ||||
|                 FindViewById(Resource.Id.QuickUnlockBlocked).Visibility = ViewStates.Visible; | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 FindViewById(Resource.Id.QuickUnlockForm).Visibility = ViewStates.Visible; | ||||
|                 FindViewById(Resource.Id.QuickUnlockBlocked).Visibility = ViewStates.Gone; | ||||
|             } | ||||
|  | ||||
|  | ||||
|  | ||||
|         } | ||||
|   | ||||
| @@ -78,20 +78,21 @@ android:paddingLeft="16dp" | ||||
| android:paddingRight="16dp" | ||||
|           android:paddingTop="16dp"> | ||||
|  | ||||
|  | ||||
|         <TextView | ||||
|                 android:id="@+id/QuickUnlock_label" | ||||
|                 android:text="@string/QuickUnlock_label" | ||||
|                 android:layout_width="fill_parent" | ||||
|                 android:layout_height="wrap_content" | ||||
|                 android:layout_below="@id/filename_label" | ||||
|                 android:textSize="14sp" | ||||
|                 /> | ||||
|          | ||||
|         <LinearLayout | ||||
|           android:layout_width="wrap_content" | ||||
|           android:layout_height="wrap_content" | ||||
|           android:orientation="horizontal"> | ||||
|           android:orientation="horizontal" | ||||
|           android:id="@+id/QuickUnlockForm"> | ||||
|  | ||||
|           <TextView | ||||
|             android:id="@+id/QuickUnlock_label" | ||||
|             android:text="@string/QuickUnlock_label" | ||||
|             android:layout_width="fill_parent" | ||||
|             android:layout_height="wrap_content" | ||||
|             android:layout_below="@id/filename_label" | ||||
|             android:textSize="14sp" | ||||
|           /> | ||||
|  | ||||
|           <EditText | ||||
|             android:inputType="textPassword" | ||||
| @@ -121,6 +122,60 @@ android:paddingRight="16dp" | ||||
|  | ||||
|         </LinearLayout> | ||||
|  | ||||
|         <LinearLayout | ||||
|           android:layout_width="match_parent" | ||||
|           android:layout_height="wrap_content" | ||||
|           android:orientation="vertical" | ||||
|           android:background="@color/md_theme_secondaryContainer" | ||||
|           android:id="@+id/QuickUnlockBlocked" | ||||
|           android:padding="16dp" | ||||
|           android:layout_gravity="center"> | ||||
|  | ||||
|           <TextView | ||||
|             android:id="@+id/quick_unlock_blocked_title" | ||||
|             android:layout_width="match_parent" | ||||
|             android:layout_height="wrap_content" | ||||
|             android:text="@string/password_based_quick_unlock_not_available" | ||||
|             android:textSize="16sp" | ||||
|             android:textStyle="bold" | ||||
|             android:gravity="center" | ||||
|             android:paddingBottom="8dp"/> | ||||
|  | ||||
|           <TextView | ||||
|             android:id="@+id/alert_message" | ||||
|             android:layout_width="match_parent" | ||||
|             android:layout_height="wrap_content" | ||||
|             android:text="@string/password_based_quick_unlock_not_available_text" | ||||
|             android:textSize="16sp" | ||||
|                             android:paddingBottom="8dp"/> | ||||
|           <LinearLayout | ||||
|             android:layout_width="match_parent" | ||||
|             android:layout_height="wrap_content" | ||||
|             android:orientation="vertical"> | ||||
|             <Button   | ||||
|               android:id="@+id/QuickUnlock_buttonEnableLock" | ||||
|               android:layout_width="wrap_content" | ||||
|               android:layout_height="wrap_content" | ||||
|               android:layout_gravity="center" | ||||
|               android:backgroundTint="@color/md_theme_secondary" | ||||
|               android:textColor="@android:color/white" | ||||
|               android:text="@string/enable_screen_lock" | ||||
|               android:fontFamily="sans-serif-medium"   /> | ||||
|  | ||||
|               <Button   | ||||
|               android:id="@+id/QuickUnlock_buttonCloseDb" | ||||
|               android:layout_width="wrap_content" | ||||
|               android:layout_height="wrap_content" | ||||
|               android:layout_gravity="center" | ||||
|               android:backgroundTint="@color/md_theme_secondary" | ||||
|               android:textColor="@android:color/white" | ||||
|               android:fontFamily="sans-serif-medium"   | ||||
|               android:text="@string/QuickUnlock_lockButton" /> | ||||
|              | ||||
|             </LinearLayout> | ||||
|              | ||||
|  | ||||
|           </LinearLayout> | ||||
|        | ||||
|         <View | ||||
|         android:id="@+id/spacing" | ||||
|   | ||||
| @@ -93,6 +93,8 @@ | ||||
|   <string name="disable_fingerprint_unlock">Disable Biometric Unlock</string> | ||||
|   <string name="enable_fingerprint_unlock">Enable full Biometric Unlock</string> | ||||
|   <string name="enable_fingerprint_quickunlock">Enable Biometric Unlock for QuickUnlock</string> | ||||
|   <string name="password_based_quick_unlock_not_available">Password-based QuickUnlock not available</string> | ||||
|   <string name="password_based_quick_unlock_not_available_text">QuickUnlock using a part of your password is blocked because screen lock is not activated on your device. This behavior is to protect you in case somebody watched you entering your QuickUnlock key.</string> | ||||
|   <string name="fingerprint_unlock_failed">Biometric Unlock failed. Decryption key was invalidated by Android OS. This usually happens if a biometric authentication or security settings were changed. </string> | ||||
|   <string name="fingerprint_disabled_wrong_masterkey">Unlocking the database failed: Invalid composite key. Biometric Unlock was disabled because apparently the stored master password is no longer valid. </string> | ||||
|   <string name="fingerprint_reenable">Please re-enable Biometric Unlock for the new master password.</string> | ||||
| @@ -319,6 +321,7 @@ | ||||
|   <string name="QuickUnlock_label_secure">Enter QuickUnlock code:</string> | ||||
|   <string name="QuickUnlock_button">QuickUnlock!</string> | ||||
|   <string name="QuickUnlock_lockButton">Close database</string> | ||||
|   <string name="enable_screen_lock">Enable screen lock</string> | ||||
|   <string name="QuickUnlockDefaultEnabled_title">Enable QuickUnlock by default</string> | ||||
|   <string name="QuickUnlockDefaultEnabled_summary">Defines whether QuickUnlock is enabled by default or not.</string> | ||||
|   <string name="ViewDatabaseSecure_title">Protect database display</string> | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <network-security-config> | ||||
|   <base-config cleartextTrafficPermitted="true"> | ||||
|   <base-config> | ||||
|     <trust-anchors> | ||||
|       <certificates src="system" /> | ||||
|       <certificates src="user" /> | ||||
|   | ||||
| @@ -351,7 +351,10 @@ namespace keepass2android | ||||
| 			QuickUnlockEnabled = enabled; | ||||
| 		} | ||||
|  | ||||
| 		public bool QuickUnlockEnabled { get; private set; } | ||||
| 		public bool ScreenLockWasEnabledWhenOpeningDatabase { get; set; } | ||||
|  | ||||
|  | ||||
|         public bool QuickUnlockEnabled { get; private set; } | ||||
|  | ||||
| 		public int QuickUnlockKeyLength { get; private set; } | ||||
|      | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 PhilippC
					PhilippC