using System; using System.IO; using System.Text; using System.Diagnostics; using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; class SafePasswordGenerator { /* Prototype for a safe password generation process for e.g. local Windows administrator accounts. Created by Alexandre Herzog under a CC-BY-SA licence (Creative Commons Attribution + ShareAlike) This work relies on two part: 1. This file which generates a safe password and encrypts it using the public key of a given certificate (see below) 2. File DecodePassword.cs which allows the decryption of the encrypted string To save the password in an encrypted form, you just need the public key of the certificate saved in file pub_cert.cer To decrypt the saved encrypted password, you need the private key of the certificate saved in file private_cert.pfx A self-signed certificate can be generated using standard Windows tools: 1. Create template template.inf using the following (adapted) template [NewRequest] Subject = "CN=W2K8-BO-DC.contoso2.com" RequestType = Cert ExportableEncrypted = TRUE KeyLength = 2048 KeySpec = AT_KEYEXCHANGE 2. Generate the certificate with command certreq -new template.inf 3. Don't bother with the popup, the self-signed certificate is already in your store (cf http://serverfault.com/questions/413976/can-you-generate-a-self-signed-certificate-on-windows-server-using-cli-tools-lik) 4. Export the certificate from your windows store (certmgr.msc - personal - certificate) Compile this program with command c:\Windows\Microsoft.NET\Framework\v2.0.50727\csc.exe SafePasswordGenerator.cs Refs: - http://technet.microsoft.com/en-us/library/dn296456.aspx (CertReq reference) - http://msdn.microsoft.com/en-us/library/system.security.cryptography.rsacryptoserviceprovider.encrypt.aspx - http://msdn.microsoft.com/en-us/library/system.security.cryptography.x509certificates.publickey(v=vs.110).aspx */ private static Encoding asciiEncoding = Encoding.ASCII; // Definition of 64 possible characters private static char[] possiblePasswordChars = new char[] {'a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z','A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z','0','1','2','3','4','5','6','7','8','9','-','_'}; private static RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider(); private static bool useOAEP = true; // RSA padding (should) avoid an attacker being able to brute force passwords and see if he gets the matching cipher text // Certificate with only a public key, used to encrypt the password private static X509Certificate2 cert = new X509Certificate2("pub_cert.cer"); // Text which will be encrypted: {0} is a placeholder for the machine name, {1} a placeholder for the password const string savedText = "Password for machine '{0}': {1}"; public static void Main(string[] args) { // Setting password to 16 chars to avoid any LM-hash related issue... String password = GetRandomString(16); string strToEncrypt = String.Format(savedText, Environment.MachineName, password); string base64String = Convert.ToBase64String( Encrypt(strToEncrypt) ); Console.WriteLine(String.Format("String '{0}' gets encrypted to base64 representation '{1}'", strToEncrypt, base64String)); // Uncomment to test decryption // Don't forget to hardcode the PIN for test purposes in method DecodeMe(byte[] b) //Console.WriteLine("Decrypted value: {0}", DecodeMe(base64String)); /* Uncomment to store this information somewhere */ /* // Stores the encrypted value on a network share File.AppendAllText( Path.Combine(@"\\network\share$\localAdminLog\", String.Format("machine_{0}.txt", Environment.MachineName)), String.Format("{0}: {1}", DateTime.Now, base64String) ); // Saves the password in a environment variable for this execution context Environment.SetEnvironmentVariable("generatedPassword", password); // Sets the given password for the local administrator user Process.Start("cmd.exe", String.Format("net user administrator {0}", password)); */ } private static byte[] Encrypt(string text) { // TODO/NEXTV check if we can rely on ECC! RSACryptoServiceProvider rsa = (RSACryptoServiceProvider)cert.PublicKey.Key; byte[] byteToEncrypt = asciiEncoding.GetBytes(text); return rsa.Encrypt(byteToEncrypt, useOAEP); } private static string GetRandomString(int length) { byte[] random = new byte[length]; rng.GetBytes(random); // The array is now filled with cryptographically strong random bytes char[] randomStr = new char[length]; for (int i=0; i < length; i++) randomStr[i] = possiblePasswordChars[random[i] % possiblePasswordChars.Length]; return new String(randomStr); } // Not needed for the encryption process itself. private static string DecodeMe(string txt) { byte[] decrypted = DecodeMe(Convert.FromBase64String(txt)); return String.Format("Decrypted string is below:\n{0}", asciiEncoding.GetString(decrypted)); } // Not needed for the encryption process itself. private static byte[] DecodeMe(byte[] b) { X509Certificate2 privateCert = new X509Certificate2("private_cert.pfx", ""); RSACryptoServiceProvider rsa = (RSACryptoServiceProvider)privateCert.PrivateKey; return rsa.Decrypt(b, useOAEP); } }