Как я потерял пароль от Android keystore, но потом смог восстановить с помощью Jetbrains Idea

ПредысторияЖило-было в Google Play Android приложение с несколькими тысячами пользователей. Через год понадобилось его обновить. Ок, запускаем Idea, выбираем «Build» — «Generate Signed APK». Вспоминаю что за это время успел пересесть в Linux, ничего страшного, выбираю файл с ключами, ввожу ранее заботливо записанный пароль… Не подходит. Хмм… Ввожу еще раз, еще… Перебор вариантов, переспрос коллег… Всё плохо.В итоге потенциально три приложения зависли в Google play, ни один из вариантов не подходит. Вспоминаю, что Windows остался на dual-boot, перезагружаюсь туда, к счастью в этом экземпляре Idea остался сохраненный пароль.

signed apk

Решение

Приложение успешно обновлено, но проблема с паролем осталась. Запрос в Jetbrains к сожалению не помог, поддержка ответила быстро, но ответ был в том духе что пароль восстановить не является возможным, дали ссылку на исходники и предложили сделать свой хак. Что в общем то логично.

Ну что же, надо думать. Так как Idea это обычное java-приложение, то возникла мысль подключить свой код к тому месту, где из хранилища считываются пароли. После прочтения топика про javaagent быстро набросал свой java agent который просто записывал в файл имена всех загружаемых классов. Все что нужно чтобы Idea запускалась с java agent, это прописать в файл idea.exe.vmoptions (или idea64.exe.vmoptions) строку вида

-javaagent: C:\projects\agent\out\artifacts\agent_jar\agent.jar После запуска с агентом текстовый файл быстро наполнился строками вида

com/intellij/openapi/ui/impl/DialogWrapperPeerImpl$MyDialog com/intellij/openapi/ui/impl/DialogWrapperPeerImpl$MyDialog$1 com/intellij/openapi/ui/impl/DialogWrapperPeerImpl$MyDialog$DialogRootPane com/intellij/openapi/ui/impl/DialogWrapperPeerImpl$MyDialog$MyWindowListener com/intellij/openapi/ui/DialogWrapper$19 com/intellij/openapi/ui/DialogWrapper$ErrorPaintingType com/intellij/ide/wizard/AbstractWizard$1 Затем жму на «Generate Signed APK» и смотрю на вывод в файле:

org/jetbrains/android/exportSignedPackage/KeystoreStep org/jetbrains/android/compiler/artifact/ApkSigningSettingsForm org/jetbrains/android/exportSignedPackage/ExportSignedPackageWizardStep Кажется, все нужное нам лежит в exportSignedPackage

Небольшое гугление, и находим исходники 2012 г.

Здесь нас привлекает кусочек кода:

String password = passwordSafe.getPassword (project, KeystoreStep.class, KEY_STORE_PASSWORD_KEY); if (password!= null) { myKeyStorePasswordField.setText (password); } password = passwordSafe.getPassword (project, KeystoreStep.class, KEY_PASSWORD_KEY); if (password!= null) { myKeyPasswordField.setText (password); } Здесь видно, что пароли вытаскивается из защищенного хранилища и сохраняются в JPasswordField (стандартный контрол Swing для ввода паролей).Осталось всего ничего — вытащить данные из текстовых полей. В этом нам поможет Javassist — библиотека для манипулирования байт-кодом «на лету». Пишем в нашем java-agent следующий кусочек кода:

public byte[] transform (final ClassLoader loader, String className, final Class classBeingRedefined, final ProtectionDomain protectionDomain, final byte[] classfileBuffer) throws IllegalClassFormatException { if («javax/swing/JPasswordField».equals (className)) { try { ClassPool cp = ClassPool.getDefault (); CtClass cc = cp.get («javax.swing.JPasswordField»); CtMethod m = cc.getDeclaredMethod («getPassword»); m.insertAfter (»{System.out.println (\«password is: \» + $_);}»); byte[] byteCode = cc.toBytecode (); cc.detach (); return byteCode; } catch (Exception ex) { ex.printStackTrace (); } } return classfileBuffer; } Что он делает? Перехватываем момент загрузки класса JPasswordField, находим в нем метод getPassword () и добавляем в конец метода наш фрагмент кода, который печатает в консоль искомый пароль ($_ это служебная переменная javassist, где лежит значение возвращаемое методом).Таким нехитрым способом пароли были восстановлены и спасены.

P.S. А пароль оказался тем же самым, что и был записан, но вводился в русской раскладке. Всё было просто на самом деле…

© Habrahabr.ru