diff --git a/main.go b/main.go index 2624b65..e987d32 100644 --- a/main.go +++ b/main.go @@ -2,119 +2,177 @@ package main import ( "fmt" + "log" + "net/url" "os" + "os/user" "runtime" "strconv" - "github.com/go-ini/ini" "strings" - "os/user" - "log" + + "gopkg.in/ini.v1" ) +// WinSCP password encryption/decryption salts. const ( - PW_MAGIC = 0xA3 - PW_FLAG = 0xFF + PasswordMagic = 0xA3 + PasswordFlag = 0xFF ) func main() { args := os.Args[1:] if len(args) != 3 && len(args) != 2 { - fmt.Println("WinSCP stored password finder\n") - fmt.Println("Registry:") - fmt.Println(" Open regedit and navigate to [HKEY_CURRENT_USER\\Software\\Martin Prikryl\\WinSCP 2\\Sessions] to get the hostname, username and encrypted password\n") - if runtime.GOOS == "windows" { - fmt.Println(" Usage winscppasswd.exe ") - } else { - fmt.Println(" Usage ./winscppasswd ") - } - fmt.Println("\n\nWinSCP.ini:") - if runtime.GOOS == "windows" { - fmt.Println(" Usage winscppasswd.exe ini []") - } else { - fmt.Println(" Usage ./winscppasswd ini []") - } - fmt.Printf(" Default value : %s\n", defaultWinSCPIniFilePath()); + // In case provided arguments doesn't match the + // application usage, print application usage message. + PrintHelp() return } + if args[0] == "ini" { - if (len(args) == 2) { - decryptIni(args[1]); + // In case 'ini' argument was provided, + // start the decryption of ini file. + var iniPath string + if len(args) == 2 { + iniPath = args[1] } else { - decryptIni(defaultWinSCPIniFilePath()); + iniPath = GetDefaultWinSCPIniFilePath() } + DecryptIni(iniPath) + return + } + + // In case any argument matches a different operation, + // perform the default decryption operation. + fmt.Println(Decrypt(args[0], args[1], args[2])) +} + +// PrintHelp prints a help message with instructions about the application usage. +func PrintHelp() { + fmt.Println("WinSCP stored password finder") + + // WinSCP's password manual decryption mode. + if runtime.GOOS == "windows" { + fmt.Println("Registry:") + fmt.Println(" Open regedit and navigate to [HKEY_CURRENT_USER\\Software\\Martin Prikryl\\WinSCP 2\\Sessions] to get the hostname, username and encrypted password") + fmt.Println(" Usage winscppasswd.exe ") } else { - fmt.Println(decrypt(args[0], args[1], args[2])) + fmt.Println(" Usage ./winscppasswd ") + } + + // WinSCP's ini file mode. + fmt.Println("\nWinSCP.ini:") + if runtime.GOOS == "windows" { + fmt.Println(" Usage winscppasswd.exe ini []") + fmt.Printf(" Default value : %s\n", GetDefaultWinSCPIniFilePath()) + } else { + fmt.Println(" Usage ./winscppasswd ini []") } } -func defaultWinSCPIniFilePath() string { +// GetDefaultWinSCPIniFilePath obtains default WinSCP configuration file. +func GetDefaultWinSCPIniFilePath() string { usr, err := user.Current() if err != nil { - log.Fatal( err ) + log.Fatal(err) } return usr.HomeDir + "\\AppData\\Roaming\\winSCP.ini" } -func decryptIni(filepath string) { +// DecryptIni decrypts all entries from a WinSCP's ini file. +func DecryptIni(filepath string) { cfg, err := ini.InsensitiveLoad(filepath) - if (err != nil) { - panic(err); + if err != nil { + panic(err) } + // Print every entry of the configuration that has password field. for _, c := range cfg.Sections() { if c.HasKey("Password") { - fmt.Printf("%s\n", strings.TrimPrefix(c.Name(), "sessions\\")); + name, _ := url.PathUnescape(strings.TrimPrefix(c.Name(), "sessions\\")) + fmt.Printf("%s\n", name) fmt.Printf(" Hostname: %s\n", c.Key("HostName").Value()) fmt.Printf(" Username: %s\n", c.Key("UserName").Value()) - fmt.Printf(" Password: %s\n", decrypt(c.Key("HostName").Value(), c.Key("UserName").Value(), c.Key("Password").Value())); + fmt.Printf(" Password: %s\n", Decrypt(c.Key("HostName").Value(), c.Key("UserName").Value(), c.Key("Password").Value())) fmt.Println("========================") } } } -func decrypt(host, username, password string) string { - key := username + host - passbytes := []byte{} +// Decrypt decripts a specific server password. +func Decrypt(host, username, password string) string { + // Build 'encryptedPasswordBytes' variable. + encryptedPasswordBytes := GetCryptedPasswordBytes(password) + + // Extract 'flag' and 'cryptedPasswordlength' variables + flag, encryptedPasswordBytes := DecryptNextCharacter(encryptedPasswordBytes) // decryptNextCharacter alters the encryptedPasswordBytes variable to remove already parsed characters. + cryptedPasswordlength, encryptedPasswordBytes := GetCryptedPasswordLength(flag, encryptedPasswordBytes) + + // Build 'clearpass' variable + clearpass := GetPassword(cryptedPasswordlength, encryptedPasswordBytes) + + // Apply correction to the 'clearpass' variable. + if flag == PasswordFlag { + // The clearpass will contians the username, host and password. + // Substring username and host from the result password. + key := username + host + clearpass = clearpass[len(key):] + } + return clearpass +} + +// GetCryptedPasswordBytes obtains the crypted password byte array. +func GetCryptedPasswordBytes(password string) []byte { + encryptedPasswordBytes := []byte{} for i := 0; i < len(password); i++ { val, _ := strconv.ParseInt(string(password[i]), 16, 8) - passbytes = append(passbytes, byte(val)) + encryptedPasswordBytes = append(encryptedPasswordBytes, byte(val)) } - var flag byte - flag, passbytes = dec_next_char(passbytes) - var length byte = 0 - if flag == PW_FLAG { - _, passbytes = dec_next_char(passbytes) + return encryptedPasswordBytes +} - length, passbytes = dec_next_char(passbytes) +// GetCryptedPasswordLength obtains crypted password length from crypted password byte array. +func GetCryptedPasswordLength(flag byte, encryptedPasswordBytes []byte) (byte, []byte) { + var cryptedPasswordlength byte = 0 + if flag == PasswordFlag { + _, encryptedPasswordBytes = DecryptNextCharacter(encryptedPasswordBytes) // Ignore two characters of the encryptedPasswordBytes. + cryptedPasswordlength, encryptedPasswordBytes = DecryptNextCharacter(encryptedPasswordBytes) // decryptNextCharacter alters the encryptedPasswordBytes variable to remove already parsed characters. } else { - length = flag - } - toBeDeleted, passbytes := dec_next_char(passbytes) - passbytes = passbytes[toBeDeleted*2:] - - clearpass := "" - var ( - i byte - val byte - ) - for i = 0; i < length; i++ { - val, passbytes = dec_next_char(passbytes) - clearpass += string(val) + cryptedPasswordlength = flag } + toBeDeleted, encryptedPasswordBytes := DecryptNextCharacter(encryptedPasswordBytes) // decryptNextCharacter alters the encryptedPasswordBytes variable to remove already parsed characters. + encryptedPasswordBytes = encryptedPasswordBytes[toBeDeleted*2:] + return cryptedPasswordlength, encryptedPasswordBytes +} - if flag == PW_FLAG { - clearpass = clearpass[len(key):] +// GetPassword obtains clear password from crypted password byte array +func GetPassword(cryptedPasswordlength byte, encryptedPasswordBytes []byte) string { + var i, character byte + var decryptedPassword string + + for i = 0; i < cryptedPasswordlength; i++ { + character, encryptedPasswordBytes = DecryptNextCharacter(encryptedPasswordBytes) // decryptNextCharacter alters the encryptedPasswordBytes variable to remove already parsed characters. + decryptedPassword += string(character) // Add decrypted character to the result variable. } - return clearpass + return decryptedPassword } -func dec_next_char(passbytes []byte) (byte, []byte) { - if len(passbytes) <= 0 { - return 0, passbytes +// DecryptNextCharacter decrypts next character from byte array. +// Alters the byte array to remove already parsed bytes. +func DecryptNextCharacter(encryptedPasswordBytes []byte) (byte, []byte) { + if len(encryptedPasswordBytes) <= 0 { + // In case encryptedPasswordBytes param was empty, + // stop the flow here returning '0'. + return 0, encryptedPasswordBytes } - a := passbytes[0] - b := passbytes[1] - passbytes = passbytes[2:] - return ^(((a << 4) + b) ^ PW_MAGIC) & 0xff, passbytes + + a := encryptedPasswordBytes[0] // Obtain first character to parse. + b := encryptedPasswordBytes[1] // Obtain second character to parse. + encryptedPasswordBytes = encryptedPasswordBytes[2:] // Remove already parsed characters. + return DecryptCharacter(a, b), encryptedPasswordBytes +} + +// DecryptCharacter decrypts character from two bytes. +func DecryptCharacter(a, b byte) byte { + return ^(((a << 4) + b) ^ PasswordMagic) & PasswordFlag }