├── LICENSE ├── README.md └── src └── main └── java └── Main.java /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Yuri Meiburg 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | WinSCP Password Recovery 2 | ======================== 3 | 4 | Decrypt stored WinSCP Passwords. 5 | 6 | Having lost my FTP password, the need quickly arose to recover the password from WinSCP. Browsing some forums on how-to's, all I found were passwordbox-unmaskers, which I strongly dislike. One post from [anoopengineer](https://github.com/anoopengineer/) gave his Go implementation of the decryption: https://github.com/anoopengineer/winscppasswd 7 | 8 | As there was no explanation whatsoever on where this information was coming from, and due to the lack of a Go compiler installed on my machine, I set out to implement the decryption algorithm myself, with help from the WinSCP source code (http://winscp.net/eng/download.php -- source). 9 | 10 | The code provides references to the corresponding C++ parts, for others to see. 11 | Start using: 12 | 13 | java Main 14 | 15 | where encryptedstring should be replaced with the registry located at: 16 | [HKEY_CURRENT_USER\Software\Martin Prikryl\WinSCP 2\Sessions\SESSION NAME\Password] (thanks to [anoopengineer](https://github.com/anoopengineer/)) 17 | > Written with [StackEdit](https://stackedit.io/). -------------------------------------------------------------------------------- /src/main/java/Main.java: -------------------------------------------------------------------------------- 1 | import java.util.ArrayList; 2 | import java.util.List; 3 | 4 | /** 5 | * Created by Yuri Meiburg on 30-4-2015. 6 | */ 7 | public class Main { 8 | 9 | /** 10 | * ./core/Security.h:#define PWALG_SIMPLE_FLAG 0xFF 11 | */ 12 | public static final int PWALG_SIMPLE_FLAG = 0xFF; 13 | 14 | /** 15 | * ./core/Security.h:#define PWALG_SIMPLE_MAGIC 0xA3 16 | */ 17 | public static final char PWALG_SIMPLE_MAGIC = 0xA3; 18 | 19 | public static List fPassword = new ArrayList(); 20 | public static String hostname, username; 21 | 22 | public static void main(String [] args){ 23 | if (args.length != 3) { 24 | System.exit(0); 25 | } 26 | 27 | hostname = args[0]; 28 | username = args[1]; 29 | 30 | for( int i=0; i< args[2].length(); ++i){ 31 | fPassword.add((char) Integer.parseInt(""+args[2].charAt(i),16)); 32 | } 33 | 34 | System.out.println("username = " + username); 35 | System.out.println("hostname = " + hostname); 36 | System.out.println("getPassword() = " + getPassword()); 37 | } 38 | 39 | 40 | /** 41 | * UnicodeString __fastcall TSessionData::GetPassword() const 42 | { 43 | return DecryptPassword(FPassword, UserName+HostName); 44 | } 45 | */ 46 | static String getPassword(){ 47 | return decryptPassword(fPassword, username + hostname); 48 | } 49 | 50 | /** 51 | * UnicodeString DecryptPassword(RawByteString Password, UnicodeString UnicodeKey, Integer) 52 | * { 53 | * UTF8String Key = UnicodeKey; 54 | * UTF8String Result(""); 55 | * Integer Index; 56 | * unsigned char Length, Flag; 57 | * 58 | * Flag = simpleDecryptNextChar(Password); 59 | * if (Flag == PWALG_SIMPLE_FLAG) 60 | * { 61 | * simpleDecryptNextChar(Password); 62 | * Length = simpleDecryptNextChar(Password); 63 | * } 64 | * else Length = Flag; 65 | * Password.Delete(1, ((Integer)simpleDecryptNextChar(Password))*2); 66 | * for (Index = 0; Index < Length; Index++) 67 | * Result += (char)simpleDecryptNextChar(Password); 68 | * if (Flag == PWALG_SIMPLE_FLAG) 69 | * { 70 | * if (Result.SubString(1, Key.Length()) != Key) Result = ""; 71 | * else Result.Delete(1, Key.Length()); 72 | * } 73 | * return UnicodeString(Result); 74 | *} 75 | */ 76 | static String decryptPassword(List password, String unicodeKey){ 77 | System.out.println("unicodeKey = " + unicodeKey); 78 | String key = unicodeKey; 79 | String result = ""; 80 | char length, flag; 81 | 82 | flag = simpleDecryptNextChar(password); 83 | System.out.println("flag = " + (int) flag); 84 | if(flag == PWALG_SIMPLE_FLAG){ 85 | /* Dummy = */ simpleDecryptNextChar(password); 86 | length = simpleDecryptNextChar(password); 87 | } 88 | else length = flag; 89 | 90 | System.out.println("length = " + (int) length); 91 | 92 | int newStart = ((int)simpleDecryptNextChar(password)*2); 93 | System.out.println("newStart = " + newStart + ", password.size() = " + password.size()); 94 | removeItems(password, 0, newStart); 95 | 96 | for(int index=0; index < length; ++index) 97 | result += simpleDecryptNextChar(password); 98 | 99 | System.out.println("result = " + result); 100 | if(flag == PWALG_SIMPLE_FLAG) 101 | { 102 | if (!result.substring(0, key.length()).equals(key)) result = ""; 103 | else result = result.substring(key.length()); 104 | } 105 | 106 | return result; 107 | } 108 | 109 | 110 | /** 111 | * unsigned char simpleDecryptNextChar(RawByteString &Str) 112 | { 113 | if (Str.Length() > 0) 114 | { 115 | unsigned char Result = (unsigned char) 116 | ~((((PWALG_SIMPLE_STRING.Pos(Str.c_str()[0])-1) << 4) + 117 | ((PWALG_SIMPLE_STRING.Pos(Str.c_str()[1])-1) << 0)) ^ PWALG_SIMPLE_MAGIC); 118 | Str.Delete(1, 2); 119 | return Result; 120 | } 121 | else return 0x00; 122 | } 123 | * @param str 124 | * @return 125 | */ 126 | static public char simpleDecryptNextChar(List str){ 127 | if(str.size() > 0){ 128 | char result = unsignedChar( 129 | ~( 130 | ( 131 | unsignedChar(str.get(0) << 4) + str.get(1) // Remove bitshift overflow bits. 132 | ) ^ PWALG_SIMPLE_MAGIC 133 | ) 134 | ); 135 | 136 | removeItems(str, 0, 2); 137 | return result; 138 | } 139 | else return 0x00; 140 | } 141 | 142 | /** 143 | * Cut off anything over 255. 144 | * @param v 145 | * @return 146 | */ 147 | static char unsignedChar(int v){ 148 | return (char) (v & 0xFF); 149 | } 150 | 151 | /** 152 | * Remove items from list 153 | */ 154 | static void removeItems(List lst, int start, int end){ 155 | for(int i=0; i