Create-KeyTab.ps1
<#PSScriptInfo
.VERSION 1.0.2 .GUID 325f7f9a-87be-42ec-ba96-c5e423718284 .AUTHOR TRAB .COMPANYNAME .COPYRIGHT .TAGS KeyTab Ktpass Key Tab .LICENSEURI https://github.com/TheRealAdamBurford/Create-KeyTab/blob/master/LICENSE .PROJECTURI https://github.com/TheRealAdamBurford/Create-KeyTab .ICONURI .EXTERNALMODULEDEPENDENCIES .REQUIREDSCRIPTS .EXTERNALSCRIPTDEPENDENCIES .RELEASENOTES .PRIVATEDATA #> <# .DESCRIPTION This scipt will generate off-line keytab files for use with Active Directory (AD). While the script is designed to work independently of AD, this script can be used with a wrapper script that uses Get-ADUser or Get-ADObject to retrieve the UPN of a samaccountname or a list of samaccountnames for use in batch processing of KeyTab creation. More information at https://therealadamburford.github.io/Create-KeyTab/ #> ########################################################## ### ### Create-KeyTab.ps1 ### ### Created : 2019-10-26 ### Modified: 2020-10-26 ### ### Created By : Adam Burford ### Modified By: Adam Burford ### ### ### Notes: Create RC4-HMAC, AES128. AES256 KeyTab file. Does not use AD. ### Password, ServicePRincipal/UPN must be set on AD account. ### Future add may include option AD lookup for Kvno, SPN and UPN. ### ### 2019-11-11 - Added custom SALT option ### 2019-11-11 - Added current Epoch Time Stamp. ### 2019-11-12 - Added -Append option ### 2019-11-12 - Added -Quiet and -NoPrompt switches for use in batch mode ### 2019-11-14 - Added support for UPN format primary/principal (e.g. host/www.domain.com). The principal is split into an array. ### The slash is removed from the SALT calculation. ### ### 2019-11-18 - Changed output text. RC4,AES128,AES256 ### 2019-11-18 - Created static nFold output. ### 2019-11-26 - Added a Get-Password function to mask password prompt input ### 2020-01-30 - Add Info for posting to https://www.powershellgallery.com ### 2020-09-15 - Added suggested use of [decimal]::Parse from "https://github.com/matherm-aboehm" to fix timestamp error on localized versions of Windows. Line 535. ### 2020-10-26 - Add KRB5_NT_SRV_HST to possible PType values ### ########################################################## ### Attribution: ### https://tools.ietf.org/html/rfc3961 ### https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-kile/936a4878-9462-4753-aac8-087cd3ca4625?redirectedfrom=MSDN ### https://github.com/dfinke/powershell-algorithms/blob/master/src/algorithms/math/euclidean-algorithm/euclideanAlgorithm.ps1 ### https://afana.me/archive/2016/12/29/how-mit-keytab-files-store-passwords.aspx/ ### http://www.ioplex.com/utilities/keytab.txt <# .SYNOPSIS This script will generate and append version 502 KeyTab files .DESCRIPTION Required Parameters -Realm : The Realm for the KeyTab -Principal : The Principal for the KeyTab. Case sensative for AES SALT. Default REALM+Principal -Password : Optional Parameters -SALT : Use a custom SALT -File : KeyTab File Path. Default = CurrentDirectory\login.keytab -KVNO : Default = 1. Exceeding 255 will wrap the KVNO. THe 32bit KVNO field is not implimented. -PType : Default = KRB5_NT_PRINCIPAL -RC4 : Generate RC4 Key -AES128 : Generate AES128 Key -AES256 : Generate AES256 Key - This is default if no Etype switch is set. -Append : Append Key Data to an existing KeyTab file. -Quiet : Suppress Text Output -NoPrompt : Suppress Write KeyTab File Prompt .EXAMPLE .\Create-KeyTab.ps1 .EXAMPLE .\Create-KeyTab.ps1 -AES256 -AES128 -RC4 .EXAMPLE .\Create-KeyTab.ps1 -AES256 -AES128 -Append .EXAMPLE .\Create-KeyTab.ps1 -AES256 -AES128 -SALT "MY.REALM.COMprincipalname" .EXAMPLE .\Create-KeyTab.ps1 -Realm "MY.REALM.COM" -Principal "principalname" -Password "Secret" -File "c:\temp\login.keytab" .NOTES Use -QUIET and -NOPROMPT for batch mode processing. .LINK https://www.linkedin.com/in/adamburford #> param ( [Parameter(Mandatory=$true,HelpMessage="REALM name will be forced to Upper Case")]$Realm, [Parameter(Mandatory=$true,HelpMessage="Principal is case sensative. It must match the principal portion of the UPN",ValueFromPipelineByPropertyName=$true)]$Principal, [Parameter(Mandatory=$false)]$Password, [Parameter(Mandatory=$false)]$SALT, [Parameter(Mandatory=$false)]$File, [Parameter(Mandatory=$false,ValueFromPipelineByPropertyName=$true)]$KVNO=1, [Parameter(Mandatory=$false)][ValidateSet("KRB5_NT_PRINCIPAL", "KRB5_NT_SRV_INST", "KRB5_NT_SRV_HST", "KRB5_NT_UID")][String[]]$PType="KRB5_NT_PRINCIPAL", [Parameter(Mandatory=$false)][Switch]$RC4, [Parameter(Mandatory=$false)][Switch]$AES128, [Parameter(Mandatory=$false)][Switch]$AES256, [Parameter(Mandatory=$false)][Switch]$Append, [Parameter(Mandatory=$false)][Switch]$Quiet, [Parameter(Mandatory=$false)][Switch]$NoPrompt ) function Get-MD4{ PARAM( [String]$String, [Byte[]]$bArray, [Switch]$UpperCase ) # Author: Larry.Song@outlook.com # https://github.com/LarrysGIT/MD4-powershell # Reference: https://tools.ietf.org/html/rfc1320 # MD4('abc'): a448017aaf21d8525fc10ae87aa6729d $Array = [byte[]]@() if($String) { $Array = [byte[]]@($String.ToCharArray() | %{[int]$_}) } if($bArray) { $Array = $bArray } # padding 100000*** to length 448, last (64 bits / 8) 8 bytes fill with original length # at least one (512 bits / 8) 64 bytes array $M = New-Object Byte[] (([math]::Floor($Array.Count/64) + 1) * 64) # copy original byte array, start from index 0 $Array.CopyTo($M, 0) # padding bits 1000 0000 $M[$Array.Count] = 0x80 # padding bits 0000 0000 to fill length (448 bits /8) 56 bytes # Default value is 0 when creating a new byte array, so, no action # padding message length to the last 64 bits @([BitConverter]::GetBytes($Array.Count * 8)).CopyTo($M, $M.Count - 8) # message digest buffer (A,B,C,D) $A = [Convert]::ToUInt32('0x67452301', 16) $B = [Convert]::ToUInt32('0xefcdab89', 16) $C = [Convert]::ToUInt32('0x98badcfe', 16) $D = [Convert]::ToUInt32('0x10325476', 16) # There is no unsigned number shift in C#, have to define one. Add-Type -TypeDefinition @' public class Shift { public static uint Left(uint a, int b) { return ((a << b) | (((a >> 1) & 0x7fffffff) >> (32 - b - 1))); } } '@ # define 3 auxiliary functions function FF([uint32]$X, [uint32]$Y, [uint32]$Z) { (($X -band $Y) -bor ((-bnot $X) -band $Z)) } function GG([uint32]$X, [uint32]$Y, [uint32]$Z) { (($X -band $Y) -bor ($X -band $Z) -bor ($Y -band $Z)) } function HH([uint32]$X, [uint32]$Y, [uint32]$Z){ ($X -bxor $Y -bxor $Z) } # processing message in one-word blocks for($i = 0; $i -lt $M.Count; $i += 64) { # Save a copy of A/B/C/D $AA = $A $BB = $B $CC = $C $DD = $D # Round 1 start $A = [Shift]::Left(($A + (FF -X $B -Y $C -Z $D) + [BitConverter]::ToUInt32($M[($i + 0)..($i + 3)], 0)) -band [uint32]::MaxValue, 3) $D = [Shift]::Left(($D + (FF -X $A -Y $B -Z $C) + [BitConverter]::ToUInt32($M[($i + 4)..($i + 7)], 0)) -band [uint32]::MaxValue, 7) $C = [Shift]::Left(($C + (FF -X $D -Y $A -Z $B) + [BitConverter]::ToUInt32($M[($i + 8)..($i + 11)], 0)) -band [uint32]::MaxValue, 11) $B = [Shift]::Left(($B + (FF -X $C -Y $D -Z $A) + [BitConverter]::ToUInt32($M[($i + 12)..($i + 15)], 0)) -band [uint32]::MaxValue, 19) $A = [Shift]::Left(($A + (FF -X $B -Y $C -Z $D) + [BitConverter]::ToUInt32($M[($i + 16)..($i + 19)], 0)) -band [uint32]::MaxValue, 3) $D = [Shift]::Left(($D + (FF -X $A -Y $B -Z $C) + [BitConverter]::ToUInt32($M[($i + 20)..($i + 23)], 0)) -band [uint32]::MaxValue, 7) $C = [Shift]::Left(($C + (FF -X $D -Y $A -Z $B) + [BitConverter]::ToUInt32($M[($i + 24)..($i + 27)], 0)) -band [uint32]::MaxValue, 11) $B = [Shift]::Left(($B + (FF -X $C -Y $D -Z $A) + [BitConverter]::ToUInt32($M[($i + 28)..($i + 31)], 0)) -band [uint32]::MaxValue, 19) $A = [Shift]::Left(($A + (FF -X $B -Y $C -Z $D) + [BitConverter]::ToUInt32($M[($i + 32)..($i + 35)], 0)) -band [uint32]::MaxValue, 3) $D = [Shift]::Left(($D + (FF -X $A -Y $B -Z $C) + [BitConverter]::ToUInt32($M[($i + 36)..($i + 39)], 0)) -band [uint32]::MaxValue, 7) $C = [Shift]::Left(($C + (FF -X $D -Y $A -Z $B) + [BitConverter]::ToUInt32($M[($i + 40)..($i + 43)], 0)) -band [uint32]::MaxValue, 11) $B = [Shift]::Left(($B + (FF -X $C -Y $D -Z $A) + [BitConverter]::ToUInt32($M[($i + 44)..($i + 47)], 0)) -band [uint32]::MaxValue, 19) $A = [Shift]::Left(($A + (FF -X $B -Y $C -Z $D) + [BitConverter]::ToUInt32($M[($i + 48)..($i + 51)], 0)) -band [uint32]::MaxValue, 3) $D = [Shift]::Left(($D + (FF -X $A -Y $B -Z $C) + [BitConverter]::ToUInt32($M[($i + 52)..($i + 55)], 0)) -band [uint32]::MaxValue, 7) $C = [Shift]::Left(($C + (FF -X $D -Y $A -Z $B) + [BitConverter]::ToUInt32($M[($i + 56)..($i + 59)], 0)) -band [uint32]::MaxValue, 11) $B = [Shift]::Left(($B + (FF -X $C -Y $D -Z $A) + [BitConverter]::ToUInt32($M[($i + 60)..($i + 63)], 0)) -band [uint32]::MaxValue, 19) # Round 1 end # Round 2 start $A = [Shift]::Left(($A + (GG -X $B -Y $C -Z $D) + [BitConverter]::ToUInt32($M[($i + 0)..($i + 3)], 0) + 0x5A827999) -band [uint32]::MaxValue, 3) $D = [Shift]::Left(($D + (GG -X $A -Y $B -Z $C) + [BitConverter]::ToUInt32($M[($i + 16)..($i + 19)], 0) + 0x5A827999) -band [uint32]::MaxValue, 5) $C = [Shift]::Left(($C + (GG -X $D -Y $A -Z $B) + [BitConverter]::ToUInt32($M[($i + 32)..($i + 35)], 0) + 0x5A827999) -band [uint32]::MaxValue, 9) $B = [Shift]::Left(($B + (GG -X $C -Y $D -Z $A) + [BitConverter]::ToUInt32($M[($i + 48)..($i + 51)], 0) + 0x5A827999) -band [uint32]::MaxValue, 13) $A = [Shift]::Left(($A + (GG -X $B -Y $C -Z $D) + [BitConverter]::ToUInt32($M[($i + 4)..($i + 7)], 0) + 0x5A827999) -band [uint32]::MaxValue, 3) $D = [Shift]::Left(($D + (GG -X $A -Y $B -Z $C) + [BitConverter]::ToUInt32($M[($i + 20)..($i + 23)], 0) + 0x5A827999) -band [uint32]::MaxValue, 5) $C = [Shift]::Left(($C + (GG -X $D -Y $A -Z $B) + [BitConverter]::ToUInt32($M[($i + 36)..($i + 39)], 0) + 0x5A827999) -band [uint32]::MaxValue, 9) $B = [Shift]::Left(($B + (GG -X $C -Y $D -Z $A) + [BitConverter]::ToUInt32($M[($i + 52)..($i + 55)], 0) + 0x5A827999) -band [uint32]::MaxValue, 13) $A = [Shift]::Left(($A + (GG -X $B -Y $C -Z $D) + [BitConverter]::ToUInt32($M[($i + 8)..($i + 11)], 0) + 0x5A827999) -band [uint32]::MaxValue, 3) $D = [Shift]::Left(($D + (GG -X $A -Y $B -Z $C) + [BitConverter]::ToUInt32($M[($i + 24)..($i + 27)], 0) + 0x5A827999) -band [uint32]::MaxValue, 5) $C = [Shift]::Left(($C + (GG -X $D -Y $A -Z $B) + [BitConverter]::ToUInt32($M[($i + 40)..($i + 43)], 0) + 0x5A827999) -band [uint32]::MaxValue, 9) $B = [Shift]::Left(($B + (GG -X $C -Y $D -Z $A) + [BitConverter]::ToUInt32($M[($i + 56)..($i + 59)], 0) + 0x5A827999) -band [uint32]::MaxValue, 13) $A = [Shift]::Left(($A + (GG -X $B -Y $C -Z $D) + [BitConverter]::ToUInt32($M[($i + 12)..($i + 15)], 0) + 0x5A827999) -band [uint32]::MaxValue, 3) $D = [Shift]::Left(($D + (GG -X $A -Y $B -Z $C) + [BitConverter]::ToUInt32($M[($i + 28)..($i + 31)], 0) + 0x5A827999) -band [uint32]::MaxValue, 5) $C = [Shift]::Left(($C + (GG -X $D -Y $A -Z $B) + [BitConverter]::ToUInt32($M[($i + 44)..($i + 47)], 0) + 0x5A827999) -band [uint32]::MaxValue, 9) $B = [Shift]::Left(($B + (GG -X $C -Y $D -Z $A) + [BitConverter]::ToUInt32($M[($i + 60)..($i + 63)], 0) + 0x5A827999) -band [uint32]::MaxValue, 13) # Round 2 end # Round 3 start $A = [Shift]::Left(($A + (HH -X $B -Y $C -Z $D) + [BitConverter]::ToUInt32($M[($i + 0)..($i + 3)], 0) + 0x6ED9EBA1) -band [uint32]::MaxValue, 3) $D = [Shift]::Left(($D + (HH -X $A -Y $B -Z $C) + [BitConverter]::ToUInt32($M[($i + 32)..($i + 35)], 0) + 0x6ED9EBA1) -band [uint32]::MaxValue, 9) $C = [Shift]::Left(($C + (HH -X $D -Y $A -Z $B) + [BitConverter]::ToUInt32($M[($i + 16)..($i + 19)], 0) + 0x6ED9EBA1) -band [uint32]::MaxValue, 11) $B = [Shift]::Left(($B + (HH -X $C -Y $D -Z $A) + [BitConverter]::ToUInt32($M[($i + 48)..($i + 51)], 0) + 0x6ED9EBA1) -band [uint32]::MaxValue, 15) $A = [Shift]::Left(($A + (HH -X $B -Y $C -Z $D) + [BitConverter]::ToUInt32($M[($i + 8)..($i + 11)], 0) + 0x6ED9EBA1) -band [uint32]::MaxValue, 3) $D = [Shift]::Left(($D + (HH -X $A -Y $B -Z $C) + [BitConverter]::ToUInt32($M[($i + 40)..($i + 43)], 0) + 0x6ED9EBA1) -band [uint32]::MaxValue, 9) $C = [Shift]::Left(($C + (HH -X $D -Y $A -Z $B) + [BitConverter]::ToUInt32($M[($i + 24)..($i + 27)], 0) + 0x6ED9EBA1) -band [uint32]::MaxValue, 11) $B = [Shift]::Left(($B + (HH -X $C -Y $D -Z $A) + [BitConverter]::ToUInt32($M[($i + 56)..($i + 59)], 0) + 0x6ED9EBA1) -band [uint32]::MaxValue, 15) $A = [Shift]::Left(($A + (HH -X $B -Y $C -Z $D) + [BitConverter]::ToUInt32($M[($i + 4)..($i + 7)], 0) + 0x6ED9EBA1) -band [uint32]::MaxValue, 3) $D = [Shift]::Left(($D + (HH -X $A -Y $B -Z $C) + [BitConverter]::ToUInt32($M[($i + 36)..($i + 39)], 0) + 0x6ED9EBA1) -band [uint32]::MaxValue, 9) $C = [Shift]::Left(($C + (HH -X $D -Y $A -Z $B) + [BitConverter]::ToUInt32($M[($i + 20)..($i + 23)], 0) + 0x6ED9EBA1) -band [uint32]::MaxValue, 11) $B = [Shift]::Left(($B + (HH -X $C -Y $D -Z $A) + [BitConverter]::ToUInt32($M[($i + 52)..($i + 55)], 0) + 0x6ED9EBA1) -band [uint32]::MaxValue, 15) $A = [Shift]::Left(($A + (HH -X $B -Y $C -Z $D) + [BitConverter]::ToUInt32($M[($i + 12)..($i + 15)], 0) + 0x6ED9EBA1) -band [uint32]::MaxValue, 3) $D = [Shift]::Left(($D + (HH -X $A -Y $B -Z $C) + [BitConverter]::ToUInt32($M[($i + 44)..($i + 47)], 0) + 0x6ED9EBA1) -band [uint32]::MaxValue, 9) $C = [Shift]::Left(($C + (HH -X $D -Y $A -Z $B) + [BitConverter]::ToUInt32($M[($i + 28)..($i + 31)], 0) + 0x6ED9EBA1) -band [uint32]::MaxValue, 11) $B = [Shift]::Left(($B + (HH -X $C -Y $D -Z $A) + [BitConverter]::ToUInt32($M[($i + 60)..($i + 63)], 0) + 0x6ED9EBA1) -band [uint32]::MaxValue, 15) # Round 3 end # Increment start $A = ($A + $AA) -band [uint32]::MaxValue $B = ($B + $BB) -band [uint32]::MaxValue $C = ($C + $CC) -band [uint32]::MaxValue $D = ($D + $DD) -band [uint32]::MaxValue # Increment end } # Output start $A = ('{0:x8}' -f $A) -ireplace '^(\w{2})(\w{2})(\w{2})(\w{2})$', '$4$3$2$1' $B = ('{0:x8}' -f $B) -ireplace '^(\w{2})(\w{2})(\w{2})(\w{2})$', '$4$3$2$1' $C = ('{0:x8}' -f $C) -ireplace '^(\w{2})(\w{2})(\w{2})(\w{2})$', '$4$3$2$1' $D = ('{0:x8}' -f $D) -ireplace '^(\w{2})(\w{2})(\w{2})(\w{2})$', '$4$3$2$1' # Output end if($UpperCase) { return "$A$B$C$D".ToUpper() } else { return "$A$B$C$D" } } function Get-PBKDF2 { param ( [Parameter(Mandatory=$true)]$PasswordString, [Parameter(Mandatory=$true)]$SALT, [Parameter(Mandatory=$true)][ValidateSet("16", "32")][String[]]$KeySize ) ### Set Key Size switch($KeySize){ "16"{ [int] $size = 16 break; } "32"{ [int] $size = 32 break; } default{} } [byte[]] $password = [Text.Encoding]::UTF8.GetBytes($PasswordString) [byte[]] $saltBytes = [Text.Encoding]::UTF8.GetBytes($SALT) #PBKDF2 IterationCount=4096 $deriveBytes = new-Object Security.Cryptography.Rfc2898DeriveBytes($password, $saltBytes, 4096) <# $hexStringSALT = Get-HexStringFromByteArray -Data $deriveBytes.Salt Write-Host "SALT (HEX):"$hexStringSALT -ForegroundColor Yellow #> return $deriveBytes.GetBytes($size) } function Encrypt-AES { param ( [Parameter(Mandatory=$true)]$KeyData, [Parameter(Mandatory=$true)]$IVData, [Parameter(Mandatory=$true)]$Data ) ### AES 128-CTS # KeySize = 16 # AESKey = Encrypt-AES -KeyData PBKdf2 -IVData IV -Data NFoldText ### AES 256-CTS # KeySize = 32 # K1 = Encrypt-AES -KeyData PBKdf2 -IVData IV -Data NFoldText # K2 = Encrypt-AES -KeyData PBKdf2 -IVData IV -Data K1 # AESKey = K1 + K2 # Create AES Object $Aes = $null $encryptor = $null $memStream = $null $cryptoStream = $null $AESKey = $null $Aes = New-Object System.Security.Cryptography.AesManaged $Aes.Mode = [System.Security.Cryptography.CipherMode]::CBC $Aes.Padding = [System.Security.Cryptography.PaddingMode]::None $Aes.BlockSize = 128 $encryptor = $Aes.CreateEncryptor($key,$IV) $memStream = new-Object IO.MemoryStream [byte[]] $AESKey = @() $cryptoStream = New-Object System.Security.Cryptography.CryptoStream($memStream, $encryptor, [System.Security.Cryptography.CryptoStreamMode]::Write) $cryptoStream.Write($Data, 0, $Data.Length) $CryptoStream.FlushFinalBlock() $cryptoStream.Close() $AESKey = $memStream.ToArray() $memStream.Close() $Aes.Dispose() return $AESKey } function Get-AES128Key { param ( [Parameter(Mandatory=$true)]$PasswordString, [Parameter(Mandatory=$true)]$SALT="" ) [byte[]] $PBKDF2 = Get-PBKDF2 -PasswordString $passwordString -SALT $SALT -KeySize 16 #[byte[]] $nFolded = (Get-NFold-Bytes -Data ([Text.Encoding]::ASCII.GetBytes("kerberos")) -KeySize 16) [byte[]] $nFolded = @(107,101,114,98,101,114,111,115,123,155,91,43,147,19,43,147) [byte[]] $Key = $PBKDF2 [byte[]] $IV = @(0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0) $AES128Key = Encrypt-AES -KeyData $key -IVData $IV -Data $nFolded return $(Get-HexStringFromByteArray -Data $AES128Key) } function Get-AES256Key { param ( [Parameter(Mandatory=$true)]$PasswordString, [Parameter(Mandatory=$true)]$SALT="" ) [byte[]] $PBKDF2 = Get-PBKDF2 -PasswordString $passwordString -SALT $SALT -KeySize 32 #[byte[]] $nFolded = (Get-NFold-Bytes -Data ([Text.Encoding]::ASCII.GetBytes("kerberos")) -KeySize 16) [byte[]] $nFolded = @(107,101,114,98,101,114,111,115,123,155,91,43,147,19,43,147) [byte[]] $Key = $PBKDF2 [byte[]] $IV = @(0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0) $k1 = Encrypt-AES -KeyData $key -IVData $IV -Data $nFolded $k2 = Encrypt-AES -KeyData $key -IVData $IV -Data $k1 $AES256Key = $k1 + $k2 return $(Get-HexStringFromByteArray -Data $AES256Key) } function Get-HexStringFromByteArray{ param ( [Parameter(Mandatory=$true,Position=0)][byte[]]$Data ) $hexString = $null $sb = New-Object System.Text.StringBuilder ($Data.Length * 2) foreach($b in $Data) { $sb.AppendFormat("{0:x2}", $b) |Out-Null } $hexString = $sb.ToString().ToUpper([CultureInfo]::InvariantCulture) return $hexString } function Get-ByteArrayFromHexString{ param ( [Parameter(Mandatory=$true)][String]$HexString ) $i = 0; $bytes = @() while($i -lt $HexString.Length) { $chars = $HexString.SubString($i, 2) $b = [Convert]::ToByte($chars, 16) $bytes += $b $i = $i+2 } return $bytes } function Get-BytesBigEndian { param ( [Parameter(Mandatory=$true)]$Value, [Parameter(Mandatory=$true)][ValidateSet("16", "32")][String[]]$BitSize ) ### Set Key Type [byte[]] $bytes = @() switch($BitSize){ "16"{ $bytes = [BitCOnverter]::GetBytes([int16]$Value) if([BitCOnverter]::IsLittleEndian){ [Array]::Reverse($bytes) } break; } "32"{ $bytes = [BitCOnverter]::GetBytes([int32]$Value) if([BitCOnverter]::IsLittleEndian){ [Array]::Reverse($bytes) } break; } default{} } return $bytes } function Get-PrincipalType { param ( [Parameter(Mandatory=$true)][ValidateSet("KRB5_NT_PRINCIPAL", "KRB5_NT_SRV_INST", "KRB5_NT_SRV_HST", "KRB5_NT_UID")][String[]]$PrincipalType ) [byte[]] $nameType = @() switch($PrincipalType){ "KRB5_NT_PRINCIPAL"{$nameType = @(00,00,00,01);break} "KRB5_NT_SRV_INST"{$nameType = @(00,00,00,02);break} "KRB5_NT_SRV_HST"{$nameType = @(00,00,00,03);break} "KRB5_NT_UID"{$nameType = @(00,00,00,05);break} default{$nameType = @(00,00,00,01);break} } return $nameType } function Create-KeyTabEntry { param ( [Parameter(Mandatory=$true)]$PasswordString, [Parameter(Mandatory=$true)]$RealmString, [Parameter(Mandatory=$true)]$Components, [Parameter(Mandatory=$false)]$SALT="", [Parameter(Mandatory=$false)]$KVNO=1, [Parameter(Mandatory=$true)][ValidateSet("KRB5_NT_PRINCIPAL", "KRB5_NT_SRV_INST", "KRB5_NT_SRV_HST", "KRB5_NT_UID")][String[]]$PrincipalType, [Parameter(Mandatory=$true)][ValidateSet("RC4", "AES128", "AES256")][String[]]$EncryptionKeyType ) ### Key Types: RC4 0x17 (23), AES128 0x11 (17), AES256 0x12 (18) ### Set Key Type [byte[]] $keyType = @() [byte[]] $sizeKeyBlock = @() switch($EncryptionKeyType){ "RC4"{ $keyType = @(00,23) $sizeKey = 16 $sizeKeyBlock = @(00,16) ### Create RC4-HMAC Key. Unicode is required for MD4 hash input. [byte[]]$password = [Text.Encoding]::Unicode.GetBytes($passwordString) $keyBlock = Get-MD4 -bArray $password -UpperCase break } "AES128"{ $keyType = @(00,17) $sizeKey = 16 $sizeKeyBlock = @(00,16) #$keyBlock = Get-AES128Key -PasswordString $passwordString -Realm $RealmString -Principal $PrincipalString -SALT $SALT $keyBlock = Get-AES128Key -PasswordString $passwordString -SALT $SALT break } "AES256"{ $keyType = @(00,18) $sizeKey = 32 $sizeKeyBlock = @(00,32) #$keyBlock = Get-AES256Key -PasswordString $passwordString -Realm $RealmString -Principal $PrincipalString -SALT $SALT $keyBlock = Get-AES256Key -PasswordString $passwordString -SALT $SALT break } default{} } ### Set Principal Type [byte[]] $nameType = @() switch($PrincipalType){ "KRB5_NT_PRINCIPAL"{$nameType = @(00,00,00,01);break} "KRB5_NT_SRV_INST"{$nameType = @(00,00,00,02);break} "KRB5_NT_SRV_HST"{$nameType = @(00,00,00,03);break} "KRB5_NT_UID"{$nameType = @(00,00,00,05);break} default{$nameType = @(00,00,00,01);break} } ### KVNO larger than 255 requires 32bit KVNO field at the end of the record $vno = @() if($kvno -le 255){ $vno = @([byte]$kvno) } else { $vno = @(00) } [byte[]]$numComponents = Get-BytesBigEndian -BitSize 16 -Value $components.Count ### To Set TimeStamp To Jan 1, 1970 - [byte[]]$timeStamp = @(0,0,0,0) ### [byte[]]$timeStamp = Get-BytesBigEndian -BitSize 32 -Value ([int]([Math]::Truncate((Get-Date(Get-Date).ToUniversalTime() -UFormat %s)))) ### 15 September 2020 Updated ### https://github.com/matherm-aboehm suggested use of [decimal]::Parse to fix timestamp error on localized versions of Windows. [byte[]]$timeStamp = Get-BytesBigEndian -BitSize 32 -Value ([int]([Math]::Truncate([decimal]::Parse((Get-Date(Get-Date).ToUniversalTime() -UFormat %s))))) ### Data size information for KeyEntry # num_components bytes = 2 # realm bytes = variable (2 bytes) + length # components array bytes = varable (2 bytes) + length for each component. Component count should be typically 1 or 2. # name type bytes = 4 # timestamp bytes = 4 # kvno bytes = 1 or 4 # Key Type bytes = 2 # Key bytes = 2 + 16 or 32 "RC4 and AES128 are 16 Byte Keys. AES 256 is 32" $sizeRealm = Get-BytesBigEndian -Value ([Text.Encoding]::UTF8.GetByteCount($realmString)) -BitSize 16 [Int32]$sizeKeyTabEntry = 2 #NumComponentsSize $sizeKeyTabEntry += 2 #RealmLength Byte Count $sizeKeyTabEntry += ([Text.Encoding]::UTF8.GetByteCount($realmString)) foreach($principal in $Components){ $sizePrincipal = ([Text.Encoding]::UTF8.GetByteCount($principal)) $sizeKeyTabEntry += $sizePrincipal + 2 } $sizeKeyTabEntry += 4 #NameType $sizeKeyTabEntry += 4 #TimeStamp $sizeKeyTabEntry += 1 #KVNO 8bit $sizeKeyTabEntry += 2 #KeyType $sizeKeyTabEntry += 2 #Key Length Count $sizeKeyTabEntry += $sizeKey $sizeTotal = Get-BytesBigEndian -Value $sizeKeyTabEntry -BitSize 32 [byte[]] $keytabEntry = @() $keytabEntry += $sizeTotal $keytabEntry += $numComponents $keytabEntry += $sizeRealm $keytabEntry += [byte[]][Text.Encoding]::UTF8.GetBytes($realmString) foreach($principal in $Components){ $sizePrincipal = Get-BytesBigEndian -Value ([Text.Encoding]::UTF8.GetByteCount($principal)) -BitSize 16 $keytabEntry += $sizePrincipal $keytabEntry += [byte[]][Text.Encoding]::UTF8.GetBytes($principal) } $keytabEntry += $nameType $keytabEntry += $timeStamp $keytabEntry += $vno $keytabEntry += $keyType $keytabEntry += $sizeKeyBlock $keytabEntry += Get-ByteArrayFromHexString -HexString $keyBlock $keytabEntryObject = [PsCustomObject]@{ Size = $sizeKeyTabEntry NumComponents = $numComponents Realm = [byte[]][Text.Encoding]::UTF8.GetBytes($realmString) Components = $components NameType = $nameType TimeStamp = $timeStamp KeyType = $keyType KeyBlock = $keyBlock KeytabEntry = $keytabEntry } return $keytabEntryObject } Function Get-Password { $passwordSecure = Read-Host -Prompt "Enter Password" -AsSecureString $passwordBSTR = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($passwordSecure) $password = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($passwordBSTR) return $password } if ([string]::IsNullOrEmpty($Password)){$Password = $(Get-Password)} if ([string]::IsNullOrEmpty($File)){$File=$(Get-Location).Path+'\login.keytab'} if($Quiet) { $Script:Silent = $true } else { $Script:Silent = $false } ### Force Realm to UPPERCASE $Realm = $Realm.ToUpper() ### The Components array splits the primary/principal. $PrincipalArray = @() $PrincipalArray = $Principal.Split(',') ### Check For Custom SALT if([string]::IsNullOrEmpty($SALT) -eq $true) { $SALT = $Realm for($i=0;$i -lt $PrincipalArray.Count;$i++){ $SALT += $($PrincipalArray[$i].Replace('/',"")) } } ### Finish spliting principal into component parts. PrincipalArray should have at most 2 elements. Testing with Java based tools, ### the keytab entry can only support one UPN. The components portion of the keytab entry appears to only be for spliting ### a UPN in an SPN format. e.g. HOST/user@dev.home $PrincipalText = $Principal $Principal = $Principal.Replace('/',",") $PrincipalArray = @() $PrincipalArray = $Principal.Split(',') [byte[]] $keyTabVersion = @(05,02) [byte[]] $keyTabEntries = @() ### Set Default Encryption to AES256 if none of the E-Type switches are set if(!$RC4 -and !$AES128 -and !$AES256){ $AES256 = $true } ### Truncate KVNO [Byte[]] $KVNO = [Byte[]](Get-BytesBigEndian -BitSize 32 -Value $KVNO) [int16] $KVNO = [int]$KVNO[3] ### Create KeyTab Entries for selected E-Types RC4/AES128/AES256 supported $keytabEntry = $null if($RC4 -eq $true){ $keytabEntry = Create-KeyTabEntry ` -realmString $Realm -Components $PrincipalArray -passwordString $Password ` -PrincipalType $PType -EncryptionKeyType RC4 -KVNO $KVNO $keyTabEntries += $keytabEntry.KeytabEntry if($Script:Silent -eq $false){ Write-Host "RC4:"$keytabEntry.KeyBlock -ForegroundColor Cyan} } $keytabEntry = $null if($AES128 -eq $true){ $keytabEntry = Create-KeyTabEntry ` -realmString $Realm -Components $PrincipalArray -passwordString $Password ` -PrincipalType $PType -EncryptionKeyType AES128 -KVNO $KVNO -SALT $SALT $keyTabEntries += $keytabEntry.KeytabEntry if($Script:Silent -eq $false){ Write-Host "AES128:"$keytabEntry.KeyBlock -ForegroundColor Cyan} } $keytabEntry = $null if($AES256 -eq $true){ $keytabEntry = Create-KeyTabEntry ` -realmString $Realm -Components $PrincipalArray -passwordString $Password ` -PrincipalType $PType -EncryptionKeyType AES256 -KVNO $KVNO -SALT $SALT $keyTabEntries += $keytabEntry.KeytabEntry if($Script:Silent -eq $false){ Write-Host "AES256:"$keytabEntry.KeyBlock -ForegroundColor Cyan} } if($Script:Silent -eq $false){ Write-Host $("Principal Type:").PadLeft(15)$PType -ForegroundColor Green Write-Host $("Realm:").PadLeft(15)$Realm -ForegroundColor Green Write-Host $("User Name:").PadLeft(15)$PrincipalText -ForegroundColor Green Write-Host $("SALT:").PadLeft(15)$SALT -ForegroundColor Green Write-Host $("Keytab File:").PadLeft(15)$File -ForegroundColor Green Write-Host $("Append File:").PadLeft(15)$Append -ForegroundColor Green Write-Host "" } if(!$NoPrompt){ Write-Host "Press Enter to Write KeyTab File /Ctrl+C to quit..." -ForegroundColor Yellow -NoNewline [void](Read-Host) Write-Host "" } if($Append -eq $true){ $fileBytes = @() if([System.IO.File]::Exists($File)){ $fileBytes += [System.IO.File]::ReadAllBytes($File) + $keyTabEntries [System.IO.File]::WriteAllBytes($File,$fileBytes) } else { $fileBytes = @() $fileBytes += $keyTabVersion $fileBytes += $keyTabEntries [System.IO.File]::WriteAllBytes($File,$fileBytes) } } else { $fileBytes = @() $fileBytes += $keyTabVersion $fileBytes += $keyTabEntries [System.IO.File]::WriteAllBytes($File,$fileBytes) } |