La méthode JPasswordField
de Swing a la méthode getPassword()
qui renvoie un tableau de caractères. Ma compréhension de cela est que le tableau peut être mis à zéro immédiatement après utilisation afin que vous n'ayez pas de choses sensibles en mémoire pendant longtemps. L'ancienne façon de récupérer le mot de passe était d'utiliser getText()
, qui renvoie un objet String, mais il est obsolète.
Donc, ma question est pourquoi il est réellement utilisé par Java pendant le processus de récupération en utilisant getPassword()
??? Pour être plus clair, je déboguais mon application de test pour autre chose **, j'ai suivi les appels et bang ... getText()
dans JPasswordField
a été appelé et, bien sûr, un objet Nice String avec mon mot de passe a été créé et traîne maintenant dans la mémoire .
Essayez par vous-même:
public class PasswordTest() {
public static void main(String[] args) {
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JPasswordField passField = new JPasswordField();
pass.addActionListener(new ActionListener() {
public ActionPerformed(ActionEvent evt) {
char[] p = passField.getPassword(); // put breakpoint
// do something with the array
}
});
frame.add(passField);
frame.setVisible(true);
frame.pack();
}
}
Question de suivi: cette utilisation "cachée" de getText()
est-elle dangereuse de quelque façon que ce soit? Bien sûr, un attaquant dédié obtiendra votre mot de passe s'il a compromis le système, je parle d'un moins dédié;)
** Je suis tombé sur cela alors que je cherchais un moyen d'afficher réellement des données sensibles sur un composant Swing sans utiliser un objet String
. Apparemment, il n'y a aucun moyen de le faire à moins que je ne sois prêt à réécrire une partie (tout?) De l'API Swing.
Cela fonctionne pour moi et vous aide à créer un mot de passe Stringified:
String passText = new String(passField.getPassword());
En fait, voici l'implémentation Sun de getPassword()
:
public char[] getPassword() {
Document doc = getDocument();
Segment txt = new Segment();
try {
doc.getText(0, doc.getLength(), txt); // use the non-String API
} catch (BadLocationException e) {
return null;
}
char[] retValue = new char[txt.count];
System.arraycopy(txt.array, txt.offset, retValue, 0, txt.count);
return retValue;
}
Le seul getText
dedans est un appel à getText(int offset, int length, Segment txt)
, qui appelle getChars(int where, int len, Segment txt)
, qui à son tour copie directement dans le tampon de Segment
. Il n'y a pas de Strings
en cours de création.
Ensuite, le tampon de Segment
est copié dans la valeur de retour et remis à zéro avant le retour de la méthode.
En d'autres termes: Il n'y a aucune copie supplémentaire du mot de passe qui traîne n'importe où . Il est parfaitement sûr tant que vous l'utilisez comme indiqué.
Ok, mon mauvais ... Toutes les cloches ont commencé à sonner dès que j'ai vu l'appel à getText () sans remarquer qu'il a été introduit par moi avec l'écouteur Action, voici un stacktrace
PasswordTest$1.getText() line: 14
PasswordTest$1(JTextField).fireActionPerformed() line: not available
PasswordTest$1(JTextField).postActionEvent() line: not available
JTextField$NotifyAction.actionPerformed(ActionEvent) line: not available
SwingUtilities.notifyAction(Action, KeyStroke, KeyEvent, Object, int) line: not available
Voici le code utilisé:
import Java.awt.event.*;
import javax.swing.*;
public class PasswordTest {
public static void main(String[] args) {
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
final JPasswordField passField = new JPasswordField() {
@Override
public String getText() {
System.err.println("Awhooa: " + super.getText()); //breakpoint
return null;
}
};
passField.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent evt) {
char[] p = passField.getPassword();
System.out.println(p);
}
});
frame.add(passField);
frame.setVisible(true);
frame.pack();
}
}
Et voici la sortie de la console:
Awhooa: secret
secret
Et pour l'appel réel à getPassword (), il me manque peut-être quelque chose, mais où le tampon de Segment est-il mis à zéro? Je vois une copie de tableau, mais pas une remise à zéro. Le tableau retourné sera mis à zéro par moi-même, mais le tableau de Segment est toujours là ...
import Java.util.Arrays;
public class ZeroingTest {
public static void main(String[] args) {
char[] a = {'a','b','c'};
char[] b = new char[a.length];
System.arraycopy(a, 0, b, 0, b.length);
System.out.println("Before zeroing: " + Arrays.toString(a) + " " + Arrays.toString(b));
Arrays.fill(a, '\0');
System.out.println("After zeroing: " + Arrays.toString(a) + " " + Arrays.toString(b));
}
}
Et la sortie:
Before zeroing: [a, b, c] [a, b, c]
After zeroing: [?, ?, ?] [a, b, c]
(J'y mets des points d'interrogation car je ne peux pas dépasser les caractères non imprimables)
-M
L'implémentation Swing est trop complexe pour être vérifiée à la main. Vous voulez des tests.
public class Pwd {
public static void main(String[] args) {
Java.awt.EventQueue.invokeLater(new Runnable() {
public void run() {
new javax.swing.JFrame("Pwd") {{
add(new javax.swing.JPasswordField() {
@Override public String getText() {
System.err.println("Awoooga!!");
return super.getText();
}
{
addActionListener(
new Java.awt.event.ActionListener() {
public void actionPerformed(
Java.awt.event.ActionEvent event
) {
// Nice.
}
}
);
}
});
setDefaultCloseOperation(DISPOSE_ON_CLOSE);
pack();
setVisible(true);
}};
}
});
}
}
Ressemble à la chaîne de commande pour l'événement d'action (inutile) pour moi. Il y aura également un autre moyen de provoquer l'effet.
Un VM vaguement moderne déplace les objets en mémoire de toute façon, donc effacer le char[]
ne fonctionne pas nécessairement.
** Je suis tombé sur cela alors que je cherchais un moyen d'afficher réellement des données sensibles sur un composant Swing sans utiliser d'objet String. Apparemment, il n'y a aucun moyen de le faire à moins que je ne sois prêt à réécrire une partie (tout?) De l'API Swing.
Vous pouvez dire à un JPasswordField
d'afficher les caractères en appelant field.setEchoChar('\0')
. Cela conserve le reste de la protection offerte par JPasswordField
(pas de String
s, pas de coupe/copie).
import javax.swing.*;
public class login extends javax.swing.JFrame {
MainProg main = new MainProg();
public login() {
initComponents();
}
/**
* This method is called from within the constructor to initialize the form.
* WARNING: Do NOT modify this code. The content of this method is always
* regenerated by the Form Editor.
*/
@SuppressWarnings("unchecked")
// <editor-fold defaultstate="collapsed" desc="Generated Code">
private void initComponents() {
jLabel1 = new javax.swing.JLabel();
jLabel2 = new javax.swing.JLabel();
txtUser = new javax.swing.JTextField();
txtPassword = new javax.swing.JTextField();
jButton1 = new javax.swing.JButton();
setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
setTitle("Log In");
setBackground(new Java.awt.Color(255, 204, 204));
setResizable(false);
jLabel1.setText("Username:");
jLabel2.setText("Password:");
jButton1.setBackground(new Java.awt.Color(204, 204, 204));
jButton1.setText("Enter");
jButton1.setOpaque(false);
jButton1.addActionListener(new Java.awt.event.ActionListener() {
public void actionPerformed(Java.awt.event.ActionEvent evt) {
jButton1ActionPerformed(evt);
}
});
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane());
getContentPane().setLayout(layout);
layout.setHorizontalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addContainerGap()
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING)
.addComponent(jButton1)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false)
.addGroup(layout.createSequentialGroup()
.addComponent(jLabel1)
.addGap(18, 18, 18)
.addComponent(txtUser, javax.swing.GroupLayout.PREFERRED_SIZE, 210, javax.swing.GroupLayout.PREFERRED_SIZE))
.addGroup(layout.createSequentialGroup()
.addComponent(jLabel2)
.addGap(20, 20, 20)
.addComponent(txtPassword))))
.addContainerGap(62, Short.MAX_VALUE))
);
layout.setVerticalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addContainerGap()
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
.addComponent(jLabel1)
.addComponent(txtUser, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
.addGap(18, 18, 18)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
.addComponent(jLabel2)
.addComponent(txtPassword, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
.addComponent(jButton1)
.addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
);
pack();
}// </editor-fold>
private void jButton1ActionPerformed(Java.awt.event.ActionEvent evt) {
String U = new String(this.txtUser.getText());
String P = new String(this.txtPass.gettext());
if(U.equals("Admin") && P.equals(Password))
{
JOptionPane.showMessageDialog(null,"Login successful!","Message",JOptionPane.INFORMATION_MESSAGE);
this.hide();
calculator.show();
}
else
{
JOptionPane.showMessageDialog(null,"Invalid username and password","Message",JOptionPane.ERROR_MESSAGE);
this.txtUser.setText("");
this.txtPassword.setText("");
}
}
/**
* @param args the command line arguments
*/
public static void main(String args[]) {
/*
* Set the Nimbus look and feel
*/
//<editor-fold defaultstate="collapsed" desc=" Look and feel setting code (optional) ">
/*
* If Nimbus (introduced in Java SE 6) is not available, stay with the
* default look and feel. For details see
* http://download.Oracle.com/javase/tutorial/uiswing/lookandfeel/plaf.html
*/
try {
for (javax.swing.UIManager.LookAndFeelInfo info : javax.swing.UIManager.getInstalledLookAndFeels()) {
if ("Nimbus".equals(info.getName())) {
javax.swing.UIManager.setLookAndFeel(info.getClassName());
break;
}
}
} catch (ClassNotFoundException ex) {
Java.util.logging.Logger.getLogger(login.class.getName()).log(Java.util.logging.Level.SEVERE, null, ex);
} catch (InstantiationException ex) {
Java.util.logging.Logger.getLogger(login.class.getName()).log(Java.util.logging.Level.SEVERE, null, ex);
} catch (IllegalAccessException ex) {
Java.util.logging.Logger.getLogger(login.class.getName()).log(Java.util.logging.Level.SEVERE, null, ex);
} catch (javax.swing.UnsupportedLookAndFeelException ex) {
Java.util.logging.Logger.getLogger(login.class.getName()).log(Java.util.logging.Level.SEVERE, null, ex);
}
//</editor-fold>
/*
* Create and display the form
*/
Java.awt.EventQueue.invokeLater(new Runnable() {
public void run() {
new login().setVisible(true);
}
});
}
// Variables declaration - do not modify
private javax.swing.JButton jButton1;
private javax.swing.JLabel jLabel1;
private javax.swing.JLabel jLabel2;
private javax.swing.JTextField txtPassword;
private javax.swing.JTextField txtUser;
// End of variables declaration
}