Last active
February 26, 2026 02:31
-
-
Save darianmiller/9de8aeb1979ef2eba9ea6069c669bca1 to your computer and use it in GitHub Desktop.
Use Powershell for fingerprint parsing. Verified working on Win11
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| echo Ensure GNU executables are in the path (typically found in C:\Program Files\Git\usr\bin) | |
| echo Use GnuPG on Windows for Code Signing git commits | |
| @echo off | |
| SETLOCAL | |
| rem useful if running batch file from Explorer with hard-coded passphrase | |
| rem cd /d %~dp0 | |
| cls | |
| echo SetupSignedCommits.bat | |
| echo https://gist.github.com/darianmiller/9de8aeb1979ef2eba9ea6069c669bca1 | |
| echo Created December 5, 2021 | |
| echo Related blog article: https://www.ideasawakened.com/post/configure-git-for-signed-commits-on-windows-using-gpg | |
| echo. | |
| echo Author: Darian Miller | |
| echo https://github.com/darianmiller | |
| echo https://www.linkedin.com/in/darianm/ | |
| echo. | |
| echo This batch file will attempt to: | |
| echo A) Optionally, run GPG to create a new keypair (RSA 4096-bit key length) for signing purposes | |
| echo Run once to create keypair and then run again in different directory to configure other local repos | |
| echo Alternatively, run once to create keypair and set the Global git config for all your local repos | |
| echo B) Optionally, navigate to GitHub or GitLab so you can paste your public key into their web interface | |
| echo Will also launch notepad containing your exported Public Key cert | |
| echo C) Optionally, configure your git config on your machine | |
| echo This sets your git user info and enables code signing with your new private key on each commit | |
| echo Settings can be configured "Global" for all repositories or "Local" for the current repository | |
| echo NOTE: If updating LOCAL settings, ensure you run this batch file within a repository workspace folder | |
| echo D) Optionally, extend the GPG agent TTL setting | |
| echo Cached passwords used by the GPG agent expire after 10 minutes of inactivity and cached for a max of 2 hours | |
| echo For ease of use, you can extend this to 1-year to prevent having to repeatedly re-input your passphrase | |
| echo. | |
| echo Directions before use: | |
| echo 1. Customize your user information and certificate parameters within this batch file | |
| echo 2. Optionally verify paths to git.exe and gpg.exe (default Git for Windows installs should work as-is) | |
| echo 3. Comment out or delete the next EXIT /B line and save your changes when ready | |
| echo 4. At a command prompt within a git workspace run: SetupSignedCommits.bat your-secret-passphrase-here | |
| EXIT /B | |
| echo. | |
| REM ---------------------- | |
| REM 1. CUSTOMIZE YOUR INFO | |
| REM ---------------------- | |
| set FirstName=John | |
| set LastName=Smith | |
| set EmailAddress=johnsmith@yourhost.com | |
| set CertComment=For signing Git commits | |
| set CertExpires=0 | |
| rem Note: set CertExpires = 0 for no expiration or set to specific number of seconds (or optionally use days/weeks/months/years designator) | |
| rem #d= days, #w = weeks, #m = months, #y = years. Example: 180 means the cert will expire in 180 seconds, 1y means 1 year | |
| rem Assume passphrase is first parameter | |
| IF ["%~1"]==[""] ( | |
| echo. | |
| echo Usage: SetupSignedCommits.bat passphrase | |
| echo. | |
| echo Please enter your passphrase | |
| EXIT /B | |
| ) | |
| rem If desired, remove the IF empty check block above and hard-code password here | |
| rem Also note: passphrase is only used when creating new keypair | |
| set Passphrase=%1 | |
| REM ---------------- | |
| REM 2. VERIFY PATHS | |
| REM ---------------- | |
| rem if you have a custom install, set default locations manually. If git/gpg is in the system PATH, simply remove the path designation. | |
| rem Download and install if missing: https://gitforwindows.org/ | |
| rem You can test at a command prompt by typing in the path configured here with a version parameter: | |
| rem "%PROGRAMFILES%\Git\usr\bin\gpg.exe" --version | |
| rem "%PROGRAMFILES%\Git\bin\git.exe" --version | |
| SET GPGCommand="%PROGRAMFILES%\Git\usr\bin\gpg.exe" | |
| if NOT EXIST %GPGCommand% ( | |
| echo Could not find gpg.exe, terminating | |
| exit /b | |
| ) | |
| SET GITCommand="%PROGRAMFILES%\Git\bin\git.exe" | |
| if NOT EXIST %GITCommand% ( | |
| echo Could not find git.exe, terminating | |
| exit /b | |
| ) | |
| REM ----------------------------------------- | |
| REM 2b. DISABLE KEYBOXD IF PRESENT (GPG 2.4+) | |
| REM ----------------------------------------- | |
| rem GPG 2.4.1+ writes "use-keyboxd" into %USERPROFILE%\.gnupg\common.conf when it | |
| rem first initializes the .gnupg directory. keyboxd.exe is NOT included in the GPG | |
| rem bundled with Git for Windows, so any keyring operation immediately fails with: | |
| rem "gpg: error running keyboxd: probably not installed" | |
| rem | |
| rem The solution is to comment out "use-keyboxd" in common.conf before key generation. | |
| rem However, common.conf is only created the first time GPG initializes .gnupg, which | |
| rem would normally happen during key generation in section A - too late to intervene. | |
| rem | |
| rem We solve this by running a harmless --list-keys call RIGHT NOW to force GPG to | |
| rem initialize the .gnupg directory and write common.conf immediately. We then patch | |
| rem the file before any key generation occurs. | |
| rem | |
| rem IMPORTANT: If a previous run of this script already failed with the keyboxd error, | |
| rem delete the .gnupg directory before running again so this section starts clean: | |
| rem rmdir /s /q "%USERPROFILE%\.gnupg" | |
| echo Initializing GPG home directory... | |
| %GPGCommand% --list-keys >nul 2>&1 | |
| SET CommonConf="%USERPROFILE%\.gnupg\common.conf" | |
| IF EXIST %CommonConf% ( | |
| findstr /C:"use-keyboxd" %CommonConf% >nul 2>&1 | |
| IF NOT ERRORLEVEL 1 ( | |
| echo Disabling keyboxd in common.conf (keyboxd is not included in Git for Windows bundled GPG^)... | |
| powershell -NoProfile -ExecutionPolicy Bypass -Command ^ | |
| "(Get-Content '%USERPROFILE%\.gnupg\common.conf') -replace '^\s*use-keyboxd\s*$','#use-keyboxd' | Set-Content -Encoding ASCII '%USERPROFILE%\.gnupg\common.conf'" | |
| echo Done. GPG will now use the traditional pubring.kbx file-based keyring. | |
| echo. | |
| ) | |
| ) | |
| REM ------------------------------------- | |
| REM A. OPTIONALLY GENERATE A NEW KEYPAIR | |
| REM ------------------------------------- | |
| CHOICE /C YN /M "Create a new keypair for %EmailAddress%?" | |
| IF ERRORLEVEL 2 GOTO NavigateOption | |
| rem Changed: use "default" usage instead of "sign" to ensure both a primary signing key | |
| rem and an encryption subkey are created. This is consistent across all GPG 2.1+ versions. | |
| rem Using "sign" alone suppresses subkey creation in GPG 2.3+ which could cause an empty | |
| rem public key export. "default" is the documented stable interface for this purpose. | |
| %GPGCommand% --batch --pinentry-mode=loopback --passphrase "%Passphrase%" --quick-generate-key "%FirstName% %LastName% (%CertComment%) <%EmailAddress%>" rsa4096 default %CertExpires% | |
| REM ----------------------- | |
| REM B. OPTIONALLY NAVIGATE | |
| REM ----------------------- | |
| rem CHOICE /C HLO returns 1 for H, 2 for L, 3 for O. | |
| rem Checks must be in descending order because IF ERRORLEVEL n means >= n. | |
| :NavigateOption | |
| CHOICE /N /C HLO /M "Adding new Public Key to GitHub, GitLab, or Other? [Enter H, L, or O]: " | |
| IF ERRORLEVEL 3 GOTO SkipPublicKeyExport | |
| IF ERRORLEVEL 2 GOTO GitLab | |
| IF ERRORLEVEL 1 GOTO GitHub | |
| GOTO SkipPublicKeyExport | |
| :GitHub | |
| rem Auto-navigate to github add GPG key page - Sign on to github as needed and then paste the public key and hit the Add gpg key button | |
| start https://github.com/settings/gpg/new | |
| rem Consider enabling vigilant mode | |
| rem start https://github.com/settings/keys | |
| GOTO ExportPublicKey | |
| :GitLab | |
| start https://gitlab.com/-/profile/gpg_keys | |
| GOTO ExportPublicKey | |
| :ExportPublicKey | |
| set TempPKFileName=%TEMP%\MyNewPublicKey\_%RANDOM%.txt | |
| rem GPG 2.4+ no longer creates intermediate directories implicitly when writing output | |
| rem files, so we must ensure the directory exists before calling --export. | |
| mkdir "%TEMP%\MyNewPublicKey" >nul 2>&1 | |
| %GPGCommand% -ao %TempPKFileName% --export %EmailAddress% | |
| start notepad.exe %TempPKFileName% | |
| echo. | |
| echo Copy your PUBLIC KEY now shown in Notepad and paste into the website to add a new GPG key to your account. | |
| echo If this temp file is empty, ensure you are using the same GPG installation that created the key. | |
| echo Verify that "%PROGRAMFILES%\Git\usr\bin\gpg.exe" is the GPG you used, with no competing Gpg4win install | |
| echo overriding your GNUPGHOME environment variable. | |
| echo. | |
| rem pause, wait to delete temp file export | |
| pause | |
| del %TempPKFileName% | |
| :SkipPublicKeyExport | |
| REM ---------------------------- | |
| REM C. OPTIONALLY CONFIGURE GIT | |
| REM ---------------------------- | |
| rem CHOICE /C GLS returns 1 for G, 2 for L, 3 for S. | |
| rem Checks must be in descending order because IF ERRORLEVEL n means >= n. | |
| CHOICE /N /C GLS /M "Configure Global, Local, or Skip updating your Git config? [Enter G, L, or S]: " | |
| IF ERRORLEVEL 3 GOTO ConfigureGitFinished | |
| IF ERRORLEVEL 2 GOTO ConfigureLocalGit | |
| IF ERRORLEVEL 1 GOTO ConfigureGlobalGit | |
| GOTO ConfigureGitFinished | |
| :ConfigureGlobalGit | |
| SET GITScope=--global | |
| GOTO ConfigureGit | |
| :ConfigureLocalGit | |
| SET GITScope=--local | |
| GOTO ConfigureGit | |
| :ConfigureGit | |
| %GITCommand% config %GITScope% user.name "%FirstName% %LastName%" | |
| %GITCommand% config %GITScope% user.email %EmailAddress% | |
| rem tell Git where to find GPG so it can sign commits | |
| %GITCommand% config %GITScope% gpg.program %GPGCommand% | |
| rem ask Git to sign all commits | |
| %GITCommand% config %GITScope% commit.gpgsign true | |
| rem Extract the public key fingerprint to configure Git for commit signing. | |
| rem | |
| rem We write GPG's --with-colons output to a temp file first, then use PowerShell | |
| rem to parse it. This avoids the nested quoting and pipe issues that occur when | |
| rem trying to use for /F with a piped command containing a path with spaces | |
| rem (e.g. "C:\Program Files\..."). | |
| rem | |
| rem The fpr: line in --with-colons output is colon-delimited with the 40-character | |
| rem fingerprint always in the 10th field (index 9). We take the first fpr: line | |
| rem only (the primary key) and ignore any subsequent ones (subkeys). | |
| rem | |
| rem Example --with-colons --fingerprint output: | |
| rem pub:u:4096:1:8F660347585C99DA:1638593497:::u:::scSC::::::23::0: | |
| rem fpr:::::::::07024D220A0BE54695F3A4808F660347585C99DA: | |
| rem uid:u::::1638593497::DAF799...::John Smith <johnsmith@null.com>::::::::::0: | |
| rem sub:u:4096:1:... | |
| rem fpr:::::::::...subkey fingerprint...: | |
| set TempGPGOut=%TEMP%\gpgout_%RANDOM%.txt | |
| %GPGCommand% --with-colons --fingerprint %EmailAddress% > "%TempGPGOut%" | |
| set keysignature= | |
| for /F %%i in ('powershell -NoProfile -Command "Get-Content \"%TempGPGOut%\" | Where-Object { $_ -match 'fpr' } | ForEach-Object { $_.Split(':')[9] } | Select-Object -First 1"') do set "keysignature=%%i" | |
| del "%TempGPGOut%" | |
| rem configure Git to use the key just created | |
| %GITCommand% config %GITScope% user.signingkey %keysignature% | |
| echo Signing key configured: %keysignature% | |
| rem view Global git settings if desired: | |
| rem notepad "%USERPROFILE%\.gitconfig" | |
| GOTO ConfigureGitFinished | |
| :ConfigureGitFinished | |
| REM ------------------------- | |
| REM D. OPTIONALLY EXTEND TTL | |
| REM ------------------------- | |
| rem don't change existing settings if any found | |
| SET AgentConfigFile="%USERPROFILE%\.gnupg\gpg-agent.conf" | |
| IF NOT EXIST %AgentConfigFile% ( | |
| rem by default you will be asked to re-input your password after 10 minutes of inactivity, or at least every 2 hours... extend this as desired | |
| rem CHOICE /C YN returns 1 for Y and 2 for N. | |
| rem Checks must be in descending order because IF ERRORLEVEL n means >= n. | |
| CHOICE /C YN /M "Extend default gpg-agent timeout? " | |
| IF ERRORLEVEL 2 GOTO AgentFinished | |
| IF ERRORLEVEL 1 GOTO ConfigureAgent | |
| GOTO AgentFinished | |
| ) | |
| :ConfigureAgent | |
| rem default-cache-ttl max age in seconds of cached passphrase (timer is reset on each use) Default is 600 seconds | |
| rem max-cache-ttl max age in seconds of cached passphrase regardless of use (force re-entry of passhprase on next use) Default is 7200 seconds | |
| rem https://www.gnupg.org/documentation/manuals/gnupg/Agent-Options.html | |
| ( | |
| echo default-cache-ttl 31536000 | |
| echo max-cache-ttl 31536000 | |
| ) > %AgentConfigFile% | |
| rem to view current settings: | |
| rem gpgconf --list-options gpg-agent | |
| rem | |
| rem technically, should reload the agent if it's running so it gets new config values: | |
| rem gpgconf.exe --reload gpg-agent | |
| :AgentFinished | |
| REM ------------- | |
| REM E. ALL DONE! | |
| REM ------------- | |
| rem for verifying current list of keys found in the ".gnupg" folder in your user homepath | |
| echo. | |
| echo Current GPG keys for %EmailAddress%: | |
| %GPGCommand% --list-keys %EmailAddress% | |
| rem Useful for testing purposes - delete new keyring after creating it | |
| rem pause | |
| rem %GPGCommand% --delete-secret-keys %EmailAddress% | |
| rem %GPGCommand% --delete-key %EmailAddress% | |
| rem for more info: | |
| rem gpg commands https://www.gnupg.org/documentation/manuals/gnupg/Operational-GPG-Commands.html | |
| rem GitHub Vigilant mode changelog: https://github.blog/changelog/2021-04-28-flag-unsigned-commits-with-vigilant-mode/ | |
| rem GitHub help page: https://docs.github.com/en/authentication/managing-commit-signature-verification/about-commit-signature-verification | |
| rem GitLab help page: https://docs.gitlab.com/ee/user/project/repository/gpg_signed_commits/index.html | |
| rem GitHub desktop currently does not support signing: https://docs.github.com/en/authentication/managing-commit-signature-verification/signing-tags | |
| rem Azure DevOps currently not supported in display: https://github.com/MicrosoftDocs/azure-devops-docs/issues/1381 also https://developercommunity.visualstudio.com/t/can-we-have-signed-commits/486953 | |
| echo Process complete! |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment