From 2e56a007fcab5ba99fd06b8a1ff52889ff0d8d70 Mon Sep 17 00:00:00 2001 From: Philipp Crocoll Date: Sat, 12 Jul 2014 06:20:56 +0200 Subject: [PATCH] first version of MasterPasswordAlgorithm ported to .net --- src/MPTest/MPTest.csproj | 72 +++++++++++ src/MPTest/Main.cs | 16 +++ src/MPTest/MpTest.cs | 69 ++++++++++ src/MPTest/Properties/AssemblyInfo.cs | 36 ++++++ src/MPTest/app.config | 3 + src/MasterPassword/MasterPassword.csproj | 57 +++++++++ src/MasterPassword/MpAlgorithm.cs | 120 ++++++++++++++++++ src/MasterPassword/MpPasswordPlistData.cs | 90 +++++++++++++ src/MasterPassword/PList.cs | 83 ++++++++++++ src/MasterPassword/Properties/AssemblyInfo.cs | 30 +++++ src/MasterPassword/packages.config | 4 + 11 files changed, 580 insertions(+) create mode 100644 src/MPTest/MPTest.csproj create mode 100644 src/MPTest/Main.cs create mode 100644 src/MPTest/MpTest.cs create mode 100644 src/MPTest/Properties/AssemblyInfo.cs create mode 100644 src/MPTest/app.config create mode 100644 src/MasterPassword/MasterPassword.csproj create mode 100644 src/MasterPassword/MpAlgorithm.cs create mode 100644 src/MasterPassword/MpPasswordPlistData.cs create mode 100644 src/MasterPassword/PList.cs create mode 100644 src/MasterPassword/Properties/AssemblyInfo.cs create mode 100644 src/MasterPassword/packages.config diff --git a/src/MPTest/MPTest.csproj b/src/MPTest/MPTest.csproj new file mode 100644 index 00000000..a81e31e8 --- /dev/null +++ b/src/MPTest/MPTest.csproj @@ -0,0 +1,72 @@ + + + + + Debug + AnyCPU + {96A3EA5A-7024-479F-A5B1-06654D0867A3} + Exe + Properties + MPTest + MPTest + v4.5 + 512 + + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + false + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + false + + + + False + ..\..\..\..\..\..\..\Program Files (x86)\Microsoft Visual Studio 11.0\Common7\IDE\PublicAssemblies\Microsoft.VisualStudio.QualityTools.UnitTestFramework.dll + + + + + + + + + + + + + + + + + + + + {2f7cb5b4-ac2a-4790-b0f3-42e6c9a060d5} + MasterPassword + + + + + \ No newline at end of file diff --git a/src/MPTest/Main.cs b/src/MPTest/Main.cs new file mode 100644 index 00000000..e7681290 --- /dev/null +++ b/src/MPTest/Main.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace MPTest +{ + class Program + { + static int Main() + { + return 0; + } + } +} diff --git a/src/MPTest/MpTest.cs b/src/MPTest/MpTest.cs new file mode 100644 index 00000000..3dfb0d15 --- /dev/null +++ b/src/MPTest/MpTest.cs @@ -0,0 +1,69 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Security.Cryptography; +using System.Text; +using MasterPassword; +using Microsoft.VisualStudio.TestTools.UnitTesting; +namespace MPTest +{ + + [TestClass()] + public class MpAlgorithmTests + { + private static byte[] HashHMAC(byte[] key, byte[] message) + { + var hash = new HMACSHA256(key); + return hash.ComputeHash(message); + } + + [TestMethod()] + public void GenerateKeyTest() + { + sbyte[] expectedRes = + { + -103, 59, -64, -27, 39, -62, 10, -76, -24, -28, -111, -75, 13, -128, -80, -101, 39, 41, -98, -22, + -42, 61, -75, 38, -107, -40, 111, 61, 108, 63, 60, 82, 92, -39, 72, 14, 14, -26, 93, 67, 83, 25, -32, 5, 32, 102, + -126, 24, 15, 65, 9, 17, 0, 123, 91, 105, -46, -99, -64, 123, -12, 80, -37, -77 + }; + var result = MpAlgorithm.GetKeyForPassword("u", "test"); + + Assert.IsTrue(expectedRes.SequenceEqual(result)); + + } + [TestMethod] + public void GenerateContentTest() + { + var key = MpAlgorithm.GetKeyForPassword("u", "test"); + string password = MpAlgorithm.GenerateContent("Long Password", "strn", key, 1, HashHMAC); + Assert.AreEqual("LapdKebv2_Tele", password); + } + + [TestMethod] + public void GenerateContentWithUmlautsTest() + { + var key = MpAlgorithm.GetKeyForPassword("uÜ", "testÖ"); + string password = MpAlgorithm.GenerateContent("Long Password", "strnÄ", key, 1, HashHMAC); + Assert.AreEqual("YepiHedo1*Kada", password); + } + + [TestMethod] + public void GenerateContentWithUmlautsAndCounterTest() + { + var key = MpAlgorithm.GetKeyForPassword("uÜ", "testÖ"); + string password = MpAlgorithm.GenerateContent("Long Password", "strnÄ", key, 42, HashHMAC); + Assert.AreEqual("Gasc3!YeluMoqb", password); + } + + [TestMethod] + public void GetKeyWithUmlautsTest() + { + var key = MpAlgorithm.GetKeyForPassword("uÜ", "testÖ"); + sbyte[] expected = + { + -53, -69, -89, 48, 122, 56, 34, 13, -70, -103, 102, 90, -96, -75, 45, 68, 43, 67, 97, 60, 84, -90, 98, -95, -2, -2, 99, -60, -121, -2, -26, -45, 53, -31, 47, 0, -46, -97, 77, -41, 63, -15, -30, 60, 4, -120, 32, 122, -94, 42, 122, -103, -61, -115, 75, -123, -15, 47, 61, -100, -119, 115, 118, 82 + }; + Assert.IsTrue(expected.SequenceEqual(key)); + } + } +} diff --git a/src/MPTest/Properties/AssemblyInfo.cs b/src/MPTest/Properties/AssemblyInfo.cs new file mode 100644 index 00000000..62ad4dd0 --- /dev/null +++ b/src/MPTest/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// Allgemeine Informationen über eine Assembly werden über die folgenden +// Attribute gesteuert. Ändern Sie diese Attributwerte, um die Informationen zu ändern, +// die mit einer Assembly verknüpft sind. +[assembly: AssemblyTitle("MPTest")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("MPTest")] +[assembly: AssemblyCopyright("Copyright © 2014")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Durch Festlegen von ComVisible auf "false" werden die Typen in dieser Assembly unsichtbar +// für COM-Komponenten. Wenn Sie auf einen Typ in dieser Assembly von +// COM zugreifen müssen, legen Sie das ComVisible-Attribut für diesen Typ auf "true" fest. +[assembly: ComVisible(false)] + +// Die folgende GUID bestimmt die ID der Typbibliothek, wenn dieses Projekt für COM verfügbar gemacht wird +[assembly: Guid("5fbae881-f9e4-4aa1-adff-4721cad61250")] + +// Versionsinformationen für eine Assembly bestehen aus den folgenden vier Werten: +// +// Hauptversion +// Nebenversion +// Buildnummer +// Revision +// +// Sie können alle Werte angeben oder die standardmäßigen Build- und Revisionsnummern +// übernehmen, indem Sie "*" eingeben: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/src/MPTest/app.config b/src/MPTest/app.config new file mode 100644 index 00000000..c5e1daef --- /dev/null +++ b/src/MPTest/app.config @@ -0,0 +1,3 @@ + + + diff --git a/src/MasterPassword/MasterPassword.csproj b/src/MasterPassword/MasterPassword.csproj new file mode 100644 index 00000000..5047d18e --- /dev/null +++ b/src/MasterPassword/MasterPassword.csproj @@ -0,0 +1,57 @@ + + + + + 11.0 + Debug + AnyCPU + {2F7CB5B4-AC2A-4790-B0F3-42E6C9A060D5} + Library + Properties + MasterPassword + MasterPassword + v4.5 + Profile7 + 512 + {786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + ..\packages\CryptSharpOfficial.2.0.0.0\lib\CryptSharp.dll + + + + + + + + \ No newline at end of file diff --git a/src/MasterPassword/MpAlgorithm.cs b/src/MasterPassword/MpAlgorithm.cs new file mode 100644 index 00000000..0a8fdd2f --- /dev/null +++ b/src/MasterPassword/MpAlgorithm.cs @@ -0,0 +1,120 @@ +using System; +using System.Linq; +using System.Text; +using CryptSharp.Utility; +using MasterPassword.Data; + + +namespace MasterPassword +{ + public partial class MpAlgorithm + { + + static readonly PList PlistData = new PList(plist); + private const int MP_N = 32768; + private const int MP_r = 8; + private const int MP_p = 2; + private const int MP_dkLen = 64; + + static sbyte[] ToSignedByteArray(byte[] unsigned) + { + sbyte[] signed = new sbyte[unsigned.Length]; + Buffer.BlockCopy(unsigned, 0, signed, 0, unsigned.Length); + return signed; + } + static byte[] ToUnsignedByteArray(sbyte[] signed) + { + byte[] unsigned = new byte[signed.Length]; + Buffer.BlockCopy(signed, 0, unsigned, 0, signed.Length); + return unsigned; + } + + public static byte[] GetKeyForPassword(string user, string password) + { + var salt = Combine(Encoding.UTF8.GetBytes("com.lyndir.masterpassword"), + IntAsByteArray(user.Length), + Encoding.UTF8.GetBytes(user)); + var ssalt = ToSignedByteArray(salt); + var spwd = ToSignedByteArray(Encoding.UTF8.GetBytes(password)); + var key = SCrypt.ComputeDerivedKey(Encoding.UTF8.GetBytes(password), + salt, + MP_N, + MP_r, + MP_p,null, MP_dkLen); + + sbyte[] signed = ToSignedByteArray(key); + return signed; + } + + + static byte[] Combine(params byte[][] arrays) + { + byte[] ret = new byte[arrays.Sum(x => x.Length)]; + int offset = 0; + foreach (byte[] data in arrays) + { + Buffer.BlockCopy(data, 0, ret, offset, data.Length); + offset += data.Length; + } + return ret; + } + static byte[] IntAsByteArray(int intValue) + { + byte[] intBytes = BitConverter.GetBytes(intValue); + if (BitConverter.IsLittleEndian) + Array.Reverse(intBytes); + return intBytes; + } + + public static String GenerateContent(string elementType, string siteName, sbyte[] key, int counter, Func hmacFunc) + { + if (counter == 0) + { + throw new Exception("counter==0 not support in .net. "); + //TODO: what does this line do in Java? + //counter = (int) (System.currentTimeMillis() / (300 * 1000)) * 300; + } + + + byte[] nameLengthBytes = IntAsByteArray(siteName.Length); + byte[] counterBytes =IntAsByteArray(counter); + + sbyte[] seed = ToSignedByteArray(hmacFunc( ToUnsignedByteArray(key), Combine( Encoding.UTF8.GetBytes("com.lyndir.masterpassword"), // + nameLengthBytes, // + Encoding.UTF8.GetBytes(siteName), // + counterBytes ) ) ); + //logger.trc( "seed is: %s", CryptUtils.encodeBase64( seed ) ); + + //Preconditions.checkState( seed.length > 0 ); + int templateIndex = seed[0] & 0xFF; // Mask the integer's sign. + //MPTemplate template = templates.getTemplateForTypeAtRollingIndex( type, templateIndex ); + //MPTemplate template = null; + + + + var templatesLongPwd = PlistData["MPElementGeneratedEntity"]["Long Password"]; + string template = templatesLongPwd[templateIndex%templatesLongPwd.Count]; + //logger.trc( "type: %s, template: %s", type, template ); + + StringBuilder password = new StringBuilder( template.Length ); + for (int i = 0; i < template.Length; ++i) { + int characterIndex = seed[i + 1] & 0xFF; // Mask the integer's sign. + + char c = template[i]; + + PList listOfCharacterSets = PlistData["MPCharacterClasses"]; + var listOfCharacters = listOfCharacterSets.Single(kvp => { return kvp.Key == c.ToString(); }).Value; + + char passwordCharacter = listOfCharacters[characterIndex % listOfCharacters.Length]; + /*logger.trc( "class: %s, index: %d, byte: 0x%02X, chosen password character: %s", characterClass, characterIndex, seed[i + 1], + passwordCharacter ); + */ + password.Append(passwordCharacter); + } + + return password.ToString(); + } + + + } +} diff --git a/src/MasterPassword/MpPasswordPlistData.cs b/src/MasterPassword/MpPasswordPlistData.cs new file mode 100644 index 00000000..e537f52b --- /dev/null +++ b/src/MasterPassword/MpPasswordPlistData.cs @@ -0,0 +1,90 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace MasterPassword +{ + public partial class MpAlgorithm + { + private const string plist = @" + + + MPElementGeneratedEntity + + Maximum Security Password + + anoxxxxxxxxxxxxxxxxx + axxxxxxxxxxxxxxxxxno + + Long Password + + CvcvnoCvcvCvcv + CvcvCvcvnoCvcv + CvcvCvcvCvcvno + CvccnoCvcvCvcv + CvccCvcvnoCvcv + CvccCvcvCvcvno + CvcvnoCvccCvcv + CvcvCvccnoCvcv + CvcvCvccCvcvno + CvcvnoCvcvCvcc + CvcvCvcvnoCvcc + CvcvCvcvCvccno + CvccnoCvccCvcv + CvccCvccnoCvcv + CvccCvccCvcvno + CvcvnoCvccCvcc + CvcvCvccnoCvcc + CvcvCvccCvccno + CvccnoCvcvCvcc + CvccCvcvnoCvcc + CvccCvcvCvccno + + Medium Password + + CvcnoCvc + CvcCvcno + + Basic Password + + aaanaaan + aannaaan + aaannaaa + + Short Password + + Cvcn + + PIN + + nnnn + + + MPCharacterClasses + + V + AEIOU + C + BCDFGHJKLMNPQRSTVWXYZ + v + aeiou + c + bcdfghjklmnpqrstvwxyz + A + AEIOUBCDFGHJKLMNPQRSTVWXYZ + a + AEIOUaeiouBCDFGHJKLMNPQRSTVWXYZbcdfghjklmnpqrstvwxyz + n + 0123456789 + o + @&%?,=[]_:-+*$#!'^~;()/. + x + AEIOUaeiouBCDFGHJKLMNPQRSTVWXYZbcdfghjklmnpqrstvwxyz0123456789!@#$%^&*() + + + +"; + } +} diff --git a/src/MasterPassword/PList.cs b/src/MasterPassword/PList.cs new file mode 100644 index 00000000..ac7a789c --- /dev/null +++ b/src/MasterPassword/PList.cs @@ -0,0 +1,83 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Xml; +using System.Xml.Linq; + +namespace MasterPassword.Data +{ + public class PList : Dictionary + { + public PList() + { + } + + public PList(string data) + { + Read(data); + } + + public void Read(string data) + { + Clear(); + + //XDocument doc = XDocument.Load(file); + XDocument doc = XDocument.Load(XmlReader.Create(new StringReader(data))); + XElement plist = doc.Element("plist"); + XElement dict = plist.Element("dict"); + + var dictElements = dict.Elements(); + Parse(this, dictElements); + } + + private void Parse(PList dict, IEnumerable elements) + { + for (int i = 0; i < elements.Count(); i += 2) + { + XElement key = elements.ElementAt(i); + XElement val = elements.ElementAt(i + 1); + + dict[key.Value] = ParseValue(val); + } + } + + private List ParseArray(IEnumerable elements) + { + List list = new List(); + foreach (XElement e in elements) + { + dynamic one = ParseValue(e); + list.Add(one); + } + + return list; + } + + private dynamic ParseValue(XElement val) + { + switch (val.Name.ToString()) + { + case "string": + return val.Value; + case "integer": + return int.Parse(val.Value); + case "real": + return float.Parse(val.Value); + case "true": + return true; + case "false": + return false; + case "dict": + PList plist = new PList(); + Parse(plist, val.Elements()); + return plist; + case "array": + List list = ParseArray(val.Elements()); + return list; + default: + throw new ArgumentException("Unsupported"); + } + } + } +} \ No newline at end of file diff --git a/src/MasterPassword/Properties/AssemblyInfo.cs b/src/MasterPassword/Properties/AssemblyInfo.cs new file mode 100644 index 00000000..30195286 --- /dev/null +++ b/src/MasterPassword/Properties/AssemblyInfo.cs @@ -0,0 +1,30 @@ +using System.Resources; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// Allgemeine Informationen über eine Assembly werden über folgende +// Attribute gesteuert. Ändern Sie diese Attributwerte, um die Informationen zu ändern, +// die einer Assembly zugeordnet sind. +[assembly: AssemblyTitle("MasterPassword")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("MasterPassword")] +[assembly: AssemblyCopyright("Copyright © 2014")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] +[assembly: NeutralResourcesLanguage("de")] + +// Versionsinformationen für eine Assembly bestehen aus den folgenden vier Werten: +// +// Hauptversion +// Nebenversion +// Buildnummer +// Revision +// +// Sie können alle Werte angeben oder die standardmäßigen Build- und Revisionsnummern +// durch Einsatz von '*', wie in nachfolgendem Beispiel: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/src/MasterPassword/packages.config b/src/MasterPassword/packages.config new file mode 100644 index 00000000..b386490c --- /dev/null +++ b/src/MasterPassword/packages.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file