0

うまくいけば、これに関する最後の質問です。これまでのところ、パスワードの作成時に使用されたランダムなソルトを渡すことができるように、独自のカスタム UserDetails および UserDetailsS​​ervice クラスを実装しました。パスワードのハッシュは SHA512 です。ただし、ログインしようとすると、常にユーザーとパスワードの組み合わせが正しくなく、その理由がわかりません。

ハッシュとソルトをブロブとしてデータベースに保存しますが、問題がどこにあるのかについてのアイデアはありますか?

Security-applicationContext.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans:beans
  xmlns:sec="http://www.springframework.org/schema/security"
  xmlns:beans="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
           http://www.springframework.org/schema/security
           http://www.springframework.org/schema/security/spring-security-3.0.xsd">

    <sec:http auto-config='true' access-denied-page="/access-denied.html">
        <!-- NO RESTRICTIONS -->        
        <sec:intercept-url pattern="/login.html" access="IS_AUTHENTICATED_ANONYMOUSLY" />
        <sec:intercept-url pattern="/*.html" access="IS_AUTHENTICATED_ANONYMOUSLY"  /> 
        <!-- RESTRICTED PAGES -->
        <sec:intercept-url pattern="/admin/*.html" access="ROLE_ADMIN" />
        <sec:intercept-url pattern="/athlete/*.html" access="ROLE_ADMIN, ROLE_STAFF" />

        <sec:form-login login-page="/login.html"
                    login-processing-url="/loginProcess"
                    authentication-failure-url="/login.html?login_error=1"
                    default-target-url="/member" />
        <sec:logout logout-success-url="/login.html"/>
    </sec:http>

    <beans:bean id="customUserDetailsService" class="PATH.TO.CustomUserDetailsService"/>
    <beans:bean id="passwordEncoder" class="org.springframework.security.authentication.encoding.ShaPasswordEncoder">
        <beans:constructor-arg value="512"/>
    </beans:bean>

    <sec:authentication-manager>
        <sec:authentication-provider user-service-ref="customUserDetailsService">
            <sec:password-encoder ref="passwordEncoder"> 
                <sec:salt-source user-property="salt"/> 
            </sec:password-encoder>
        </sec:authentication-provider>
    </sec:authentication-manager>
</beans:beans>

CustomUserDetails.java

public class CustomUserDetails implements UserDetails {

    private int userID;
    private String username;
    private String password;
    private Collection<GrantedAuthority> authorities;
    private boolean accountNonExpired;
    private boolean accountNonLocked;
    private boolean credentialsNonExpired;
    private boolean enabled;
    private String salt;

    public CustomUserDetails() {
    }

    public CustomUserDetails(int userID, Collection<GrantedAuthority> authorities, String username, String password, boolean accountNonExpired, boolean accountNonLocked, boolean credentialsNonExpired, boolean enabled, String salt) {
        this.userID = userID;
        this.authorities = authorities;
        this.username = username;
        this.password = password;
        this.accountNonExpired = accountNonExpired;
        this.accountNonLocked = accountNonLocked;
        this.credentialsNonExpired = credentialsNonExpired;
        this.enabled = enabled;
        this.salt = salt;
    }

    @Override
    public Collection<GrantedAuthority> getAuthorities() {
        return authorities;
    }

    public int getUserID() {
        return userID;
    }

    public void setUserID(int userID) {
        this.userID = userID;
    }

    @Override
    public String getPassword() {
        return password;
    }

    @Override
    public String getUsername() {
        return username;
    }

    @Override
    public boolean isAccountNonExpired() {
        return accountNonExpired;
    }

    @Override
    public boolean isAccountNonLocked() {
        return accountNonLocked;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return credentialsNonExpired;
    }

    @Override
    public boolean isEnabled() {
        return enabled;
    }

    public String getSalt() {
        return salt;
    }

    public void setAccountNonExpired(boolean accountNonExpired) {
        this.accountNonExpired = accountNonExpired;
    }

    public void setAccountNonLocked(boolean accountNonLocked) {
        this.accountNonLocked = accountNonLocked;
    }

    public void setAuthorities(Collection<GrantedAuthority> authorities) {
        this.authorities = authorities;
    }

    public void setCredentialsNonExpired(boolean credentialsNonExpired) {
        this.credentialsNonExpired = credentialsNonExpired;
    }

    public void setEnabled(boolean enabled) {
        this.enabled = enabled;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public void setSalt(String salt) {
        this.salt = salt;
    }
}

CustomUserDetailsS​​ervice.java

public class CustomUserDetailsService implements UserDetailsService {

    private User_dao userDao;

    @Autowired
    public void setUserDao(User_dao userDao) {
        this.userDao = userDao;
    }

    @Override
    public CustomUserDetails loadUserByUsername(String username) throws UsernameNotFoundException, DataAccessException {
        MyUser myUser = new MyUser();
        myUser.setUsername(username);
        try {
            userDao.getUserByUsername(myUser);
        } catch (Throwable e) {
        }
        if (myUser == null) {
            throw new UsernameNotFoundException("Username not found", username);
        } else {
            List<GrantedAuthority> authList = new ArrayList<GrantedAuthority>();
            authList.add(new GrantedAuthorityImpl(myUser.getUserRole().getAuthority()));

            int userID = myUser.getUserID();
            boolean accountNonExpired = true;
            boolean accountNonLocked = myUser.isNonLocked();
            boolean credentialsNonExpired = true;
            boolean enabled = myUser.isEnabled();
            String password = "";
            String salt = "";

            password = new String(myUser.getHash);
            salt = new String(myUser.getSalt());
            CustomUserDetails user = new CustomUserDetails(userID, authList, username, password, accountNonExpired, accountNonLocked, credentialsNonExpired, enabled, salt);
            return user;
        }
    }
}

パスワードの作成

public byte[] generateSalt() throws NoSuchAlgorithmException {
    SecureRandom random = SecureRandom.getInstance("SHA1PRNG");
    byte[] salt = new byte[20];
    random.nextBytes(salt);
    return salt;
}

public byte[] generateHash(byte[] salt, String pass) throws NoSuchAlgorithmException {
    MessageDigest digest = MessageDigest.getInstance("SHA-512");
    digest.update(salt);
    byte[] hash = digest.digest(pass.getBytes());
    return hash;
}

呼び出し方法:

byte[] salt = generateSalt();
byte[] hash = generateHash(salt, password);
Which I then store in the db.
4

2 に答える 2

3

私は同じ元の問題を抱えていましたが、これは決して答えられなかったので、これが将来誰かの時間を節約できることを期待して:

Spring-Security は、ダイジェストを比較する前に、デフォルトで中括弧を追加します。私はそれを見逃して、何時間も車輪を回転させました(d'oh)。

中括弧で囲まれたソルト値を保存 (または生成) するようにしてください(つまり、Spring が '{salt}' と言うとき、それらは「中括弧を開く + あなたのソルト値 + 中括弧を閉じる」という意味です)。

これはほとんどの人にとって明らかなことだと思いますが、最終的にデバッグするまで気づきませんでした。

于 2013-02-16T18:39:57.987 に答える
-3

各ユーザーのパスワードに使用されるソルトをデータベースのソルト列に保存すると (一般的ではありますが)、脆弱性が生じることは指摘しておく価値があると思います。そもそもソルティングの理由は、侵害されたデータベースに対する辞書攻撃を防ぐためです。攻撃者がデータベースにアクセスでき、salt が使用されていない場合、標準的な辞書のすべての単語に共通のハッシュ アルゴリズムを適用して、ハッシュの新しい辞書を作成できます。データベースでこれらの単語の 1 つに一致するものを見つけると、独自の辞書でマッピングを調べて、アルゴリズムが適用されたときにそのハッシュを生成する元のハッシュされていない単語を見つけます。そして出来上がり!攻撃者はパスワードを持っています。

さて...塩を適用し、塩がユーザーごとに異なる場合、その攻撃計画に巨大なモンキーレンチを投入します. しかし...データベース内の各ユーザーのソルトを保存する場合(そして列に「ソルト」という名前を付けることで明らかにする場合)、実際にはこの攻撃計画にそれほど干渉していません。

私が知っている最も安全なアプローチはこれです。

  1. ユーザーテーブルにソルト列がありません。
  2. UserDetails クラスに getSalt() メソッドを実装します。ユーザーが登録したときに設定され、決して変更されない他のユーザー属性を返すようにします。たとえば、参加日。それを UserDetails クラスにハードコーディングされた文字列リテラル/定数と連結します。

このようにして、ソルトはすべてのユーザーに固有のものになります。ソルトとして使用された値は、データベースを見ても明らかではありません。また、攻撃者がソルトの一部に何が使用されているかを推測したとしても、ソルトの REST を知るためにソース コードにアクセスする必要もあります。これは、実際の問題が発生する前に、データベースとアプリケーション コードの両方が危険にさらされる必要があることを意味します。

ここで述べたことすべての利点を理解していると仮定すると、ここで述べたことは実際にすでに行っていることよりも実装が簡単であるという点で、かなりの利点があります。正しいことがより簡単であることが判明したとき、それが大好きです!

于 2012-05-18T19:20:38.057 に答える