4

Scenario:

  • User A logs into Windows (8 upwards, Server 2008R2 upwards).
  • The user accesses a 3rd party service through software with username and password.
  • I need to provide a "Keep logged in"-experience, hence I need a symmetrically obfuscated PW stored password locally (user specific - inside %localappdata%/software/....)

Problem:

So far my predecessor used Symmetric Rijndael with Cipher Block Chaining and hardcoded digest to obfuscate the password. Any user & any machine which got the %localappdata% file copied over could authenticate successfully with User A's stored creds. I don't like.

Constraints for obfuscation:

  • I want to tie the password obfuscation somehow to the logged in windows user/maschine
  • changing f.e. the windows users password should not invalidate the obfuscated password
  • moving the file over to a different machine with a (cloned) User A should invalidate the deobfuscation
  • moving the file from User A to User B should invalidate the deobfuscation

Jon Galloway's blog which is from 2008 but made me look into DPAPI: as I understand it, System.Security.Cryptography.ProtectedData with DataProtectionScope.CurrentUser should disable other Users to deobfustcate the password.

Question:

What do I provide as entropy value to disable access for a cloned User A on a different machine or should I go another route altogether?


Some code to test the ProtectedData stuff from MSDN with a string passphrase:

// modified from https://learn.microsoft.com/de-de/dotnet/api/system.security.cryptography.protecteddata
using System;
using System.Linq;
using System.Security.Cryptography;
using System.Text;

public class DataProtectionSample
{
  static string GetHexString (byte [] data)
    => string.Join (" ", data.Select (d => d.ToString ("x2")));

  static string GetUnicodeString (byte [] data)
    => Encoding.Unicode.GetString (data);

  static byte [] GetBytesFromUnicode (string data)
    => Encoding.Unicode.GetBytes (data);

  public static void Main ()
  {
    // Create byte array for additional entropy when using Protect method.
    byte [] entropy = { 9, 8, 7, 6, 5 };
    byte [] secret = GetBytesFromUnicode ("Cräzy obfuscated paß phrâse");

    //Encrypt the data.
    byte [] encryptedSecret = Protect( secret , entropy );

    Console.WriteLine ("The encrypted byte array is:");
    Console.WriteLine (GetHexString (encryptedSecret));

    // Decrypt the data and store in a byte array.
    byte [] originalData = Unprotect( encryptedSecret, entropy );
    Console.WriteLine ("{0}The original data is:", Environment.NewLine);
    Console.WriteLine (GetUnicodeString (originalData));

    Console.ReadLine ();
  }

  static byte [] Protect (byte [] data, byte [] entropy)
  {
    try
    {
      return ProtectedData.Protect (data, entropy, DataProtectionScope.CurrentUser);
    }
    catch (CryptographicException e)
    {
      Console.WriteLine (e.ToString ());
      return null;
    }
  }

  static byte [] Unprotect (byte [] data, byte [] entropy)
  {
    try
    {
      return ProtectedData.Unprotect (data, entropy, DataProtectionScope.CurrentUser);
    }
    catch (CryptographicException e)
    {
      Console.WriteLine (e.ToString());
      return null;
    }
  }
}

I am aware that impersonating User A will make the obfuscation void - hence obfuscation not encryption. What I am after, is a kind of tougher version of "can't read plaintext pw from file" with a second factor of "need to be logged in with same windows user" and "on the same machine".

I also looked into How should I ethically approach user password storage for later plaintext retrieval? which does not quite help as it's answers mostly assume some kind of web scenario with password reset mechanism which I do not have.

Disclaimer: My passwords are random generated Keypass stored values. I do not use "Keep logged in" features. Storing passwords in plaintext is evil, storing passwords symmetrically encrypted can be broken and has to be avoided. Still need to provide above feature ...

https://www.harmj0y.net/blog/redteaming/operational-guidance-for-offensive-user-dpapi-abuse/ suggests that DPAPI has several exploitable "flaws" as soon as you are able to impersonate the user in any way or form.

Jason Aller
  • 3,541
  • 28
  • 38
  • 38
Patrick Artner
  • 50,409
  • 9
  • 43
  • 69
  • You do not need to change the entropy - different user cannot decrypt even with the same entropy data – Sir Rufo Feb 19 '20 at 12:05
  • Do the accounts have admin rights? If they do then `DataProtectionScope` wont do you any good. – Charles Feb 20 '20 at 07:24
  • @charles Maybe. Why would being a local administrator make any difference to this? If logged in as administrator I would still not be the same user and not able to `ProtectedData.Unprotect` some other users stored data? – Patrick Artner Feb 20 '20 at 07:54
  • 1
    @PatrickArtner an administrator can create services in session 0, session 0 or the `System` account can impersonate any user. With `PsExec` for example i can spawn a process under any user of the computer. So im pretty sure `Only threads running under the current user context can unprotect the data` would be useless if im admin. Plus a program in session 0 can easily read your memory. Alternatively as admin you can grab ownership of files and folders. – Charles Feb 20 '20 at 08:17
  • 1
    As a Service you can get a user token with `WTSQueryUserToken` and then use `CreateProcessAsUser` to spawn a process under the user of your choice. – Charles Feb 20 '20 at 08:43
  • Note to all future users, the OPs problem and solution are not ideal. This is not the appropriate way to deal with passwords (at all), and not how modern authentication and authorization should work. The OP is managing a legacy code base, if you are in a similar situation i would suggest starting this from scratch and use modern authentication techniques – TheGeneral Nov 16 '21 at 21:41

1 Answers1

0

I went with a combination of ProtectData.Protect and it's counterpart ProtectData.Unprotect adding in an entropy calculated from some values pertinent/different to each user.

The resulting bytestream is compressed using a DeflateStream and stored.

For decoding I do similar things just in reverse using the appropriate functions.

I am aware: this is password-obfuscation at best but for now it is "good enough" - and it is far better then using the same Crypto.dll with fixed key-phrases that work for everyone without access to a specific windows account/pc with its stored secrets.

Patrick Artner
  • 50,409
  • 9
  • 43
  • 69