Store passwords in scripts securely through Windows Credential Manager API

Post your working scripts, libraries and tools.
geek
Posts: 1052
Joined: 02 Oct 2013, 22:13
Location: GeekDude
Contact:

Store passwords in scripts securely through Windows Credential Manager API

Post by geek » 19 Apr 2023, 15:56

v1 thread

Windows has a built-in credential manager (check your start menu for "Credential Manager") that stores secrets encrypted by your user profile. They cannot be decrypted without your user account credentials (and will be lost if you force an account password reset with something like a recovery boot device). They will be decrypted automatically by Windows when asked for by a program/script running under your user account, so you won't be prompted for a password to decrypt them beyond the windows logon screen you used to log in in the first place.

You can access this credential store to put sensitive information in that you don't want other users to access, or someone who steals your laptop to access. Do note that if your laptop does not have a proper password set on it, it won't be properly encrypted against them just signing in as you and pulling all the passwords. Be sure to choose a unique name, as any program/script running as you shares this store and can access passwords stored in it by that name.

You can put passwords into the store either using AHK, or by opening the credential manager, going to "Windows Credentials", and clicking "Add a generic credential".

Bonus points for those of you in a corporate domain, these passwords will follow your domain profile around to any other computer you sign in to. (if you don't want that, change PERSIST to 2).

Code: Select all

#Requires AutoHotkey v2.0

if !CredWrite("AHK_CredForScript1", "SomeUsername", "SomePassword")
	MsgBox "failed to write cred"

if (cred := CredRead("AHK_CredForScript1"))
	MsgBox cred.name "," cred.username "," cred.password
else
	MsgBox "Cred not found"

if !CredDelete("AHK_CredForScript1")
	MsgBox "Failed to delete cred"

if (cred := CredRead("AHK_CredForScript1"))
	MsgBox cred.name "," cred.username "," cred.password
else
	MsgBox "Cred not found"

CredWrite(name, username, password) {
	cred := Buffer(24 + A_PtrSize * 7, 0)
	cbPassword := StrLen(password) * 2
	NumPut("UInt", 1               , cred,  4+A_PtrSize*0) ; Type = CRED_TYPE_GENERIC
	NumPut("Ptr",  StrPtr(name)    , cred,  8+A_PtrSize*0) ; TargetName
	NumPut("UInt", cbPassword      , cred, 16+A_PtrSize*2) ; CredentialBlobSize
	NumPut("Ptr",  StrPtr(password), cred, 16+A_PtrSize*3) ; CredentialBlob
	NumPut("UInt", 3               , cred, 16+A_PtrSize*4) ; Persist = CRED_PERSIST_ENTERPRISE (roam across domain)
	NumPut("Ptr",  StrPtr(username), cred, 24+A_PtrSize*6) ; UserName
	return DllCall("Advapi32.dll\CredWriteW",
		"Ptr", cred, ; [in] PCREDENTIALW Credential
		"UInt", 0,   ; [in] DWORD        Flags
		"UInt" ; BOOL
	)
}

CredDelete(name) {
	return DllCall("Advapi32.dll\CredDeleteW",
		"WStr", name, ; [in] LPCWSTR TargetName
		"UInt", 1,    ; [in] DWORD   Type
		"UInt", 0,    ; [in] DWORD   Flags
		"UInt" ; BOOL
	)
}

CredRead(name) {
	pCred := 0
	DllCall("Advapi32.dll\CredReadW",
		"Str",  name,   ; [in]  LPCWSTR      TargetName
		"UInt", 1,      ; [in]  DWORD        Type = CRED_TYPE_GENERIC (https://learn.microsoft.com/en-us/windows/win32/api/wincred/ns-wincred-credentiala)
		"UInt", 0,      ; [in]  DWORD        Flags
		"Ptr*", &pCred, ; [out] PCREDENTIALW *Credential
		"UInt" ; BOOL
	)
	if !pCred
		return
	name := StrGet(NumGet(pCred, 8 + A_PtrSize * 0, "UPtr"), 256, "UTF-16")
	username := StrGet(NumGet(pCred, 24 + A_PtrSize * 6, "UPtr"), 256, "UTF-16")
	len := NumGet(pCred, 16 + A_PtrSize * 2, "UInt")
	password := StrGet(NumGet(pCred, 16 + A_PtrSize * 3, "UPtr"), len/2, "UTF-16")
	DllCall("Advapi32.dll\CredFree", "Ptr", pCred)
	return {name: name, username: username, password: password}
}

fattredd
Posts: 1
Joined: 20 Apr 2023, 12:06

Re: Store passwords in scripts securely through Windows Credential Manager API

Post by fattredd » 20 Apr 2023, 12:28

Hey geek! Thanks for helping me out with this yesterday. You and CloakerSmoker (I assume based on their discord handle) helped me understand DllCall, Num/StrGet, and Ptrs a lot.
I've made sure to make a note in my library of where your code came from as well. Keep up the good work!

BB32205
Posts: 4
Joined: 04 May 2022, 14:25

Re: Store passwords in scripts securely through Windows Credential Manager API

Post by BB32205 » 13 Mar 2024, 19:25

Hi @geek I tried posting this reply/question in the v2 thread, but am not familiar on how I'm supposed to do that.

Sorry to be such a n00b, but how is this implemented? I've verified I'm running v2.x

I've created a Generic credential via Win Cred Mgr, and would like to use this script to pass the credentials to a variable in my own script.

I've tried using this example (based on your work), to no avail.
https://superuser.com/questions/537578/encrypt-plaintext-password-in-autohotkey-file

Can you point me to any examples where snippets of your script will hand off the password read from the generic user to a variable/input of another script?


[Mod action: Topic moved from the v1 version of this thread.]

User avatar
boiler
Posts: 16981
Joined: 21 Dec 2014, 02:44

Re: Store passwords in scripts securely through Windows Credential Manager API

Post by boiler » 13 Mar 2024, 19:40

BB32205 wrote: Hi @geek I tried posting this reply/question in the v2 thread, but am not familiar on how I'm supposed to do that.
Why would you think replying in that thread (now this one since your post has been moved) is any different than replying in the v1 thread?

geek
Posts: 1052
Joined: 02 Oct 2013, 22:13
Location: GeekDude
Contact:

Re: Store passwords in scripts securely through Windows Credential Manager API

Post by geek » 13 Mar 2024, 19:46

BB32205 wrote:
13 Mar 2024, 19:25
Can you point me to any examples where snippets of your script will hand off the password read from the generic user to a variable/input of another script?
The code and example for how to use it in AHKv2 is at the top of this thread. To recap, copy the CredRead function into your script and then you can access any credential like this, replacing AHK_CredForScript1 with the name of the credential as you entered it into Credential Manager:

Code: Select all

if (cred := CredRead("AHK_CredForScript1"))
	MsgBox cred.name "," cred.username "," cred.password
else
	MsgBox "Cred not found"

reddyshyam
Posts: 38
Joined: 24 Jul 2023, 04:34

Re: Store passwords in scripts securely through Windows Credential Manager API

Post by reddyshyam » 14 Mar 2024, 00:11

Hi @geek,

Good one there! This could turn out to be a password manager with a nice GUI? :)

geek
Posts: 1052
Joined: 02 Oct 2013, 22:13
Location: GeekDude
Contact:

Re: Store passwords in scripts securely through Windows Credential Manager API

Post by geek » 14 Mar 2024, 11:05

reddyshyam wrote: Hi @geek,

Good one there! This could turn out to be a password manager with a nice GUI? :)
I wouldn't recommend it. The Windows credential manager is a great way to keep credentials out of scripts and secure at rest, but it does not have all the attributes you need for a password manager. Security aside, there are several ways you can completely trash the credential store (like forcing an account password reset) that makes it an unsuitable option compared to other password managers which use encrypted database files that can backed up. Although you could make AHK export the credential manager credentials to an encrypted backup file, once you've implemented the encrypted backup file support that file immediately becomes a better option for secure data storage than using the Windows credential manager in the first place.

No, the Windows Credential Manager is best suited to storing "remember me" type data. It's not appropriate as the "source of truth" of credentials for an account you are not comfortable with having to recover later because the password was lost after a Windows Update or some other credential store wiping event.

reddyshyam
Posts: 38
Joined: 24 Jul 2023, 04:34

Re: Store passwords in scripts securely through Windows Credential Manager API

Post by reddyshyam » 14 Mar 2024, 11:15

geek wrote:
14 Mar 2024, 11:05
reddyshyam wrote: Hi @geek,

Good one there! This could turn out to be a password manager with a nice GUI? :)
I wouldn't recommend it. The Windows credential manager is a great way to keep credentials out of scripts and secure at rest, but it does not have all the attributes you need for a password manager. Security aside, there are several ways you can completely trash the credential store (like forcing an account password reset) that makes it an unsuitable option compared to other password managers which use encrypted database files that can backed up. Although you could make AHK export the credential manager credentials to an encrypted backup file, once you've implemented the encrypted backup file support that file immediately becomes a better option for secure data storage than using the Windows credential manager in the first place.

No, the Windows Credential Manager is best suited to storing "remember me" type data. It's not appropriate as the "source of truth" of credentials for an account you are not comfortable with having to recover later because the password was lost after a Windows Update or some other credential store wiping event.
Hi @geek ,

Thanks for getting back and the clarification. I see you point and understand. :)

Have a good day!

BB32205
Posts: 4
Joined: 04 May 2022, 14:25

Re: Store passwords in scripts securely through Windows Credential Manager API

Post by BB32205 » 26 Mar 2024, 20:25

How would I modify this section so that it's only prompting for a password, and updating an existing Windows Generic credential? The why is this: our priv. access password changes every 24 hours.

Code: Select all

NewPassGui() {
  myGui := Gui("-MinimizeBox -MaximizeBox +MinSize2000x180 +MaxSize2000x180", "New/change pass")
  myGui.Add("Text", , "Credential name:")
  myGui.Add("Edit", "w100 vnewKey")
  myGui.Add("Text", , "User:")
  myGui.Add("Edit", "w100 vnewUser")
  myGui.Add("Text", , "Pass:")
  myGui.Add("Edit", "w100 vnewPass")
  ogcButtonAddPass := myGui.Add("Button", "Default x30 w60", "&Add Pass")
  ogcButtonAddPass.OnEvent("Click", ButtonAddPass.Bind("Normal"))
  myGui.OnEvent("Escape", GuiEscape)

  ButtonAddPass(*) {
    oSaved := myGui.Submit()
    newKey := oSaved.newKey
    newUser := oSaved.newUser
    newPass := oSaved.newPass
    cred_key := "AHK_" . newKey
    if CredWrite(cred_key, newUser, newPass)
      tToolTip("Saved creds for " newUser " [" cred_key "]", 3000)
    else
      tToolTip("Failed to save creds for " newKey, 3000)
  }
  GuiEscape(*) {
    myGui.Destroy()
  }

  myGui.Show()
}

droyo
Posts: 4
Joined: 20 Mar 2023, 22:57

Re: Store passwords in scripts securely through Windows Credential Manager API

Post by droyo » 27 Mar 2024, 08:21

Some time ago I tried turning this code into a class to make it easier to manage my stored credentials.
I have been using this class ever since. I recently made a GUI for this class.
It is possible to create new credentials and change the stored ones (those created by the class, since I use a prefix at the beginning of those credentials).
By right clicking the credential in the list view, you can delete it, edit it or copy the password.
I believe it is possible to change all stored credentials, leaving the prefix blank, but I have never tested it thoroughly.

Code: Select all

^!+p::CredManager.ShowGUI()
class CredManager {
	static prefix := "Ahk_"
	static filter := this.prefix "*"

	; get using myCred := CredManager["myCred"] then use myCred.user / myCred.pass or just CredManager["myCred"].pass
	; set using CredManager["myCred"] := {user: "myUser", pass: "myPass"}
	static __Item[name] {
		get => this.Read(name)
		set {
			if Type(value) = "Object" && value.HasOwnProp("user") & value.HasOwnProp("pass") 
				this.Write(name, value.user, value.pass)
		}
	}
	
	static AddPrefixName(name) => (SubStr(name, 1, StrLen(this.prefix)) = this.prefix ? "" : this.prefix) . name
	static DelPrefixName(name) =>  SubStr(name, 1, StrLen(this.prefix)) = this.prefix ? SubStr(name, StrLen(this.prefix)+1) : name

	static Write(name, username, password) {
		name := this.AddPrefixName(name)
		cred := Buffer(24 + A_PtrSize * 7, 0)
		cbPassword := StrLen(password) * 2
		NumPut("UInt", 1               , cred,  4+A_PtrSize*0) ; Type = CRED_TYPE_GENERIC
		NumPut("Ptr",  StrPtr(name)    , cred,  8+A_PtrSize*0) ; TargetName
		NumPut("UInt", cbPassword      , cred, 16+A_PtrSize*2) ; CredentialBlobSize
		NumPut("Ptr",  StrPtr(password), cred, 16+A_PtrSize*3) ; CredentialBlob
		NumPut("UInt", 3               , cred, 16+A_PtrSize*4) ; Persist = CRED_PERSIST_ENTERPRISE (roam across domain)
		NumPut("Ptr",  StrPtr(username), cred, 24+A_PtrSize*6) ; UserName
		return DllCall("Advapi32.dll\CredWriteW",
			"Ptr", cred, ; [in] PCREDENTIALW Credential
			"UInt", 0,   ; [in] DWORD        Flags
			"UInt" 		 ; BOOL
		)
	}

	static Delete(name) {
		return DllCall("Advapi32.dll\CredDeleteW",
			"WStr", this.AddPrefixName(name), ; [in] LPCWSTR TargetName
			"UInt", 1,    ; [in] DWORD   Type
			"UInt", 0,    ; [in] DWORD   Flags
			"UInt" 		  ; BOOL
		)
	}

	static Read(name) {
		pCred := 0
		DllCall("Advapi32.dll\CredReadW",
			"Str", this.AddPrefixName(name),   	 ; [in]  LPCWSTR      TargetName
			"UInt", 1,      ; [in]  DWORD        Type = CRED_TYPE_GENERIC (https://learn.microsoft.com/en-us/windows/win32/api/wincred/ns-wincred-credentiala)
			"UInt", 0,      ; [in]  DWORD        Flags
			"Ptr*", &pCred, ; [out] PCREDENTIALW *Credential
			"UInt" 			; BOOL
		)
		if !pCred
			return {name: false, user: "", pass: ""}
		name := StrGet(NumGet(pCred, 8 + A_PtrSize * 0, "UPtr"), 256, "UTF-16")
		usrExist := NumGet(pCred, 24 + A_PtrSize * 6, "UPtr")
		username := usrExist = 0 ? "" : StrGet(usrExist, 256, "UTF-16")
		len := NumGet(pCred, 16 + A_PtrSize * 2, "UInt")
		password := StrGet(NumGet(pCred, 16 + A_PtrSize * 3, "UPtr"), len/2, "UTF-16")
		DllCall("Advapi32.dll\CredFree", "Ptr", pCred)
		return {name: name, user: username, pass: password}
	}

	static Count() 	     => this.Enumerate().count
	static GetAllCreds() => this.Enumerate().creds
	static Enumerate(filter := this.filter) {  
		flags := count := creds := 0
		DllCall("Advapi32.dll\CredEnumerateW", "Str", filter, "UInt", flags, "UInt*", &count, "Ptr*", &creds)
		SetTimer ()=>DllCall("Advapi32.dll\CredFree", "Ptr", creds), -2000
		return {count: count, creds: creds}
	}

	static ListAll(filter := this.filter) {  
		List := []
		creds := this.Enumerate(filter)
		Loop creds.count {
			PtrCred := NumGet(creds.creds, (A_Index - 1) * A_PtrSize, "Int")
			if PtrCred != 0 {
				name := StrGet(NumGet(PtrCred, 8, "UPtr"), 256, "UTF-16")
				List.Push(this.Read(name))
			}
		}
		return List
	}

	static ShowGUI() {
		MyGui := Gui("", "Ahk_CredManager")
		MyGui.SetFont("s12")
		LV1 := MyGui.Add("ListView", "x10 y10 r10 w510", ["Name","User"])
		LV1.OnEvent("ContextMenu", ShowContextMenu)
		MyGui.Add("Button", "x430 y+5 w80 h25", "Create").OnEvent("Click", EditOrNewCred.Bind(""))
		MyGui.SetFont("s10")
		SB := MyGui.Add("StatusBar",, "")
		LoadCreds()
		MyGui.Show()
		SetIcon(MyGui)

		LoadCreds() {
			LV1.Delete()
			pList := this.ListAll()
			SB.SetText("--- " pList.Length " credentials ---")
			col1 := 1
			loop pList.Length {
				nameCred := this.DelPrefixName(pList[A_Index].name)
				n := LV1.add(, nameCred, pList[A_Index].user)
				LV1.Modify(n, "Vis")
				col1 := 9*StrLen(nameCred) > col1 ? 9*StrLen(nameCred) : col1
			}
			LV1.ModifyCol(1, (col1 > 200 ? 200 : col1) " Sort") 
			LV1.ModifyCol(2, 490-(col1 > 200 ? 200 : col1)) 
			LV1.Opt("+Redraw")
		}

		EditOrNewCred(cred := "", *) {
			eGui := Gui("", "Ahk_CredManager")
			eGui.SetFont("s12")
			eGui.AddText("w300 CGray", "Credendial Name")
			eGui.AddEdit("xp y+2 w200 vName")
			eGui.AddText("xp y+5 w330 CGray", "Username")
			eGui.AddEdit("xp y+2 w265 vUser")
			eGui.AddText("xp y+2 w330 CGray", "Password")
			eGui.AddEdit("xp y+2 w265 +Password vPass")
			eGui.AddCheckbox("x+10 yp h23 -Tabstop", "").OnEvent("Click", (gObj, *)=>eGui["Pass"].Opt((gObj.Value ? "-" : "+") . "Password"))
			eGui.Add("Button", "x225 y35 w80 h25", "Save").OnEvent("Click", (*) {
				if eGui["Name"].Text != "" and eGui["Pass"].Text != "" {
					if eGui["Name"].Text !== cred
						this.Delete(cred)
					this.Write(eGui["Name"].Text, eGui["User"].Text, eGui["Pass"].Text)	 
					eGui.Destroy()
					LoadCreds()
				} else
					MsgBox("Credendial Name and Password can't be NULL", "Credentials")
			})
			eGui.Show("w320")
			SetIcon(eGui)
			if cred != "" {
				eGui["Name"].Text := cred ;, eGui["Name"].Opt("ReadOnly")
				eGui["User"].Text := CredManager[cred].user
				eGui["Pass"].Text := CredManager[cred].pass
			}
		}
		
		ShowContextMenu(LV, Item, IsRightClick, X, Y) {
			credName := LV.GetText(Item, 1)
			if credName != "" {
				CMenu := Menu()
				CMenu.Add("Edit", ObjBindMethod(EditOrNewCred,,credName))
				CMenu.Add("Copy", (*) => A_Clipboard := CredManager[credName].pass)
				CMenu.Add("")
				CMenu.Add("Delete", (*) {
					if MsgBox("you really want to DELETE this credential?`n" credName, "Credentials", "YesNo") = "Yes" {
						CredManager.Delete(credName)
						LoadCreds()
					}
				})
				CMenu.Show(X, Y)
			}
		}

		SetIcon(MyGui) {
			;hIcon := DllCall("LoadImage", "UInt", 0, "Str", "C:\Program Files\PowerToys\WinUI3Apps\Assets\FileLocksmith\Icon.ico", "UInt", 1, "Int", 0, "Int", 0, "UInt", 0x10)
			DllCall("PrivateExtractIcons", "str", "imageres.dll", "int", 225, "int", 256, "int", 256, "uint*", &hIcon := 0, "uint*", 0, "uint", 1, "uint", 0, "int")
			SendMessage 0x80, 0, hIcon, ,"ahk_id " MyGui.Hwnd
			SendMessage 0x80, 1, hIcon, ,"ahk_id " MyGui.Hwnd
		}
	}
}

Post Reply

Return to “Scripts and Functions (v2)”