Java 在数据库中存储哈希和salt后哈希结果不一致

Java 在数据库中存储哈希和salt后哈希结果不一致,java,hash,oracle-sqldeveloper,Java,Hash,Oracle Sqldeveloper,尝试启动并运行身份验证服务,但存在一个相当严重的问题,即如果我将salt存储在数据库中,然后检索它并尝试使用用户提交的密码生成哈希,则即使密码相同,它也会生成不正确的哈希 例如,如果我使用我的代码发布 { "Email":"stackOverflow@gmail.com", "Password":"foo" } 对其进行哈希运算,并生成以下值: 哈希密码:��-o]��+ztu��K� 盐:[B@681d1bc7 如果我随后检索salt并尝试进行身份验证,则会收到哈希密码:

尝试启动并运行身份验证服务,但存在一个相当严重的问题,即如果我将salt存储在数据库中,然后检索它并尝试使用用户提交的密码生成哈希,则即使密码相同,它也会生成不正确的哈希

例如,如果我使用我的代码发布

{
    "Email":"stackOverflow@gmail.com",
    "Password":"foo"
}
对其进行哈希运算,并生成以下值:

  • 哈希密码:��-o]��+ztu��K�
  • 盐:[B@681d1bc7
如果我随后检索salt并尝试进行身份验证,则会收到哈希密码:

  • 哈希密码:r��7.����^J!/QF
盐是一样的[B@681d1bc7,因此,除非存在不一致的数据结构,否则我认为这是好的

数据库是Oracle SQL,ID是一个数字,电子邮件、密码和密码是Varchars


代码如下:

认证控制器:

/*
 * TODO: Change me
 */
package entities;

import importsJSON.JSONObject;
import importsJSON.JSONParser;
import java.io.InputStream;
import java.sql.SQLException;

public class AuthenticationController {

//<editor-fold desc="Logic related to instantiation of an AC object">
/**
 * Logic related to instantiation of an AC object.
 * To instantiate an AC object, due to the method of the Singleton Pattern, 
 * AuthenticationController ac = new AuthenticationController();
 * will not work, as the constructor is private.
 * Instead calls are made as such:
 * AuthenticationController ac = AuthenticationController.getInstance();
 * 
 * This means that there will only ever be a single AuthenticationController, which
 * bears the benefit that concurrency and multiple instances are eliminated as possibilities, 
 * something arguably desirable for such functionality as login management.
 * It does need to be noted however, that this can cause a potential bottleneck and harms scalability
 * of a single server. While it is good we don't have to worry about multiple logins or 
 * "losing" a logged in user, it's also bad in the sense that we can't have multiple AC instances.
 * If 3 users require authentication simultaneously, they cannot have have their requests 
 * delegated to different AC instances to process them faster.
 * Such a system would ideally be better for scalability, but would require a great deal more work in order to ensure 
 * everything is safe and issues like concurrency are avoided.
 */
private static AuthenticationController instance = new AuthenticationController();

private AuthenticationController(){}

public static AuthenticationController getInstance(){
    return instance;
}
//</editor-fold>

public String getMessage(){
    return "HelloWorld";
}

public String authenticateUser(String rawData) throws SQLException, Exception{     
    //Variables for building a User to evaluate
    String email = "";
    PasswordManager passwordManager = new PasswordManager();
    JSONObject user = null;
    byte[] salt = null;
    int userID;

    String results ="";

    //Variables for request evaluation
    Boolean badInput = false;
    String hashedUserPassword = null;
    String storedPassword = null;
    UserModel userModel = new UserModel();


    //Convert the input to a JSON Object
    JSONParser parser = new JSONParser();
    user = (JSONObject)parser.parse(rawData);     

    //Get User Email and Password  
    try{
        email = user.get("Email").toString();
        passwordManager.setPtPassword(user.get("Password").toString()); 
    }
    catch(Exception ex){
        badInput = true;
    }

    //If the user has not provided bad data, eg null values
    if(!badInput){
        //Create SQL query to search database for a row with matching email
        //Note that we only need the ID, Password and Salt, anything else is unnecessary
        String parameterlessQuery = "Select ID, PASSWORD, PASSWORD_SALT From Users Where UPPER(EMAIL) = UPPER(?)";
        String[] params = {email};
        SQLQuery query = new SQLQuery(parameterlessQuery, params);   

        if((email!=null)&&(email.length()>0)){
            query = authenticateUser(query);
        }
        else{
            query.setErrorCode("22000");
        }

        //Retrieve variables from results list
        if(!query.getResults().isEmpty()){
            Object[] userToVerify = query.getResults().get(0);                

            //userModel.setID(userToVerify[0].toString());
            userModel.sethPassword(userToVerify[1].toString());
            userModel.setSalt(userToVerify[2].toString());

            //Use the salt and provided password to create hashed password
            String userPassword = passwordManager.getPtPassword();
            hashedUserPassword  = passwordManager.generateHash(userPassword, userModel.getSalt().getBytes());
            System.out.println("New hashed: "+ hashedUserPassword+"   Hashed: "+ userModel.gethPassword()+ "    Salt:"+userModel.getSalt());
        }
    }            

    //Finally we need to convert them 


    //Compare  hashed password from Database to newly hashed password
    return (hashedUserPassword+"   "+ userModel.gethPassword());


    //If match, user is logged in

    //If no match, user receieves a negative reply informing them of failure.

}

//Method for submitting a query to a database
/** 
 * May seem redundant, however this is necessary in order to get back the query 
 * which was passed in so properties of the query can be examined.
 * Initialises a database controller and passes the query to be executed. 
 * @param query
 * @return 
 */
public SQLQuery authenticateUser(SQLQuery query){        
    DatabaseController databaseController = new DatabaseController();
    return databaseController.submitQuery(query, 3);
}
package services;

import entities.AuthenticationController;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import javax.ejb.Stateless;
import javax.ws.rs.Consumes;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;

@Path("/Authenticate")
@Stateless
public class AuthenticationService {    

@POST
@Path("/")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public String loginUser(InputStream data) throws Exception {        
    AuthenticationController authController = AuthenticationController.getInstance();

    StringBuilder builder = new StringBuilder();

    try{
        BufferedReader in = new BufferedReader(new InputStreamReader(data));
        String line = null;
        while((line = in.readLine())!= null){
            builder.append(line);
        }
    } catch (IOException ex) {

    }
    return authController.authenticateUser(builder.toString());      
}
}
package entities;

import java.sql.*;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 *
 * @author Andrew
 * Class controlling access to databases using SQL queries.
 */

public class DatabaseController extends AbstractController{

//<editor-fold desc="Constructors and class variables">    
String targetDB;//The database it shall use to connect to
String username;//The username needed to connect to the target database
String password;//The password needed to connect to the target database

//By default, this method shall be used.
public DatabaseController(){
   this.targetDB = [redacted];
   this.username = [redacted];
   this.password = [redacted];
} 

//Should we have more than one database to manipulate.
public DatabaseController(String targetDB, String username, String password){
   this.targetDB = targetDB;
   this.username = username;
   this.password = password;
}
//</editor-fold>

//Formats and submits the query to the given database.
public SQLQuery submitQuery(SQLQuery query, int columns){        
    try {

        //Using the Oracle DB connection Driver            
        Class.forName("oracle.jdbc.driver.OracleDriver");
        //Open a connection to the target Database
        Connection connection = DriverManager.getConnection(targetDB, username, password);           

        //Build then Execute Query         
        //This may look a bit weird. Unfortunately it's the result of the nuances of the 
        //classes used, (PreparedStatement instead of Statement), however the benefit of 
        //doing so is protection from SQL injections.
        PreparedStatement statement = connection.prepareStatement(query.getParameterlessQuery());           

        if(query.getQueryParams().length!=0){
            String[] params = query.getQueryParams();

            for(int i=0; i< params.length; i++){
                statement.setString((i+1), params[i]);                   
            }
        }

        //query.setDebug(statement.toString());


        query.setResponse(statement.executeQuery());

        //If there is a result set expected, then save the results before they are lost on connection close
        while(query.getResponse().next()){                
            Object[] temp = new Object[columns];
            for (int i = 0; i < columns; i++){
                temp[i] = query.getResponse().getObject(i+1);
            }

            query.getResults().add(temp);
        }


        //Close Connection
        connection.close();
   } catch (ClassNotFoundException ex) {
        Logger.getLogger(DatabaseController.class.getName()).log(Level.SEVERE, null, ex);
        query.setDebug("driver missing");
   } catch (SQLException ex) {
        for (Throwable e : ex) {
            if (e instanceof SQLException) {                   
                query.queryFail(((SQLException)e).getSQLState(), ((SQLException)e).getMessage());

                System.out.println(query.getErrorCode() + "\n"+ query.getErrorMessage());
            }
        }
   }
    return query;
}

}
package services;

import entities.DatabaseController;
import entities.PasswordManager;
import entities.SOAPEmailValidator;
import entities.SQLQuery;
import importsJSON.JSONObject;
import importsJSON.JSONParser;
import importsJSON.ParseException;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.security.NoSuchAlgorithmException;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.ejb.Stateless;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;

/**
 *
 * @author Andrew
 * A Rather simple class that controls the registration services associated with 
 * our Web Service.
 */
@Path("/Register")
@Stateless
public class RegistrationService {    

    //Register user service
    /**
     * The default method for the /Register context of web services, meaning it is accessed
     * by a URI of form host/API/Register. 
     * Unlike most of the other methods, this is a post method and requires a sample of JSON 
     * data providing an email and password from the user in a form similar to:
     * {
     *      "Email":"[emailValue]",
     *      "Password":"[passwordValue]"
     * }
     * 
     * Everything else is then auto generated as necessary, and used to insert the user in to 
     * the database.
     * 
     * @param data
     * @return 
     */
    @POST
    @Path("/")
    @Produces(MediaType.APPLICATION_JSON)
    @Consumes(MediaType.APPLICATION_JSON)
    public Response registerUser(InputStream data){
        StringBuilder builder = new StringBuilder();

        //Variables for the building of the user to be inserted
        String email = "";
        String ptPassword = "";
        JSONObject user = null;
        PasswordManager password = new PasswordManager();
        byte[] salt = null;

        try{
            //Read in the submitted JSON
            BufferedReader in = new BufferedReader(new InputStreamReader(data));
            String line = null;
            while ((line = in.readLine()) != null) {
                builder.append(line);
            }

            //Convert the JSON String submitted by client to a JSON Object
            JSONParser p = new JSONParser();
            user = (JSONObject)p.parse(builder.toString());

            //Extract relevant values from the JSON Object
            email = user.get("Email").toString();
            ptPassword = user.get("Password").toString();

            //Generate Salt
            salt = password.generateSalt();

            //Use Salt to hash the password
            password.setHashedPassword(password.generateHash(ptPassword, salt));

        } catch (IOException ex) {
            Logger.getLogger(RegistrationService.class.getName()).log(Level.SEVERE, null, ex);        
        } catch (ParseException ex) {
            Logger.getLogger(RegistrationService.class.getName()).log(Level.SEVERE, null, ex);
        } catch (NoSuchAlgorithmException ex) {
            Logger.getLogger(RegistrationService.class.getName()).log(Level.SEVERE, null, ex);
        } catch (Exception ex) {
            Logger.getLogger(RegistrationService.class.getName()).log(Level.SEVERE, null, ex);
        }


        //Attempt to create a query
        String parameterlessQuery = "Insert into users (ID, EMAIL, PASSWORD, PASSWORD_SALT) VALUES (SEQ_PERSON_ID.nextval,?,?,?)";
        String[] params = {email, password.getHashedPassword(), password.saltToString(salt)};
        System.out.println("New Hashed:             "+"Hashed: "+ password.getHashedPassword() + "    Salt: "+ password.saltToString(salt));
        SQLQuery query = new SQLQuery(parameterlessQuery, params);

        //Check to ensure that the data is not null
        if((email!=null&&ptPassword!=null)&&(email.length()>0&&(ptPassword.length()>0))){
            query = registerUser(query);
        }else
        {
            query.setErrorCode("22000");
        }

        //If the error code suggests that the user has been created successfully, return a 200 OK message
        //to client
        if (query.getErrorCode().matches("00000")||query.getErrorCode().matches("24000")){
            return Response.status(200).entity("User Created successfully.").build();
        }
        else{//If not, return an error message with the SQLState code thrown
            return Response.status(400).entity("Failed, Error Code: "+query.getErrorCode()).build();
        }
    }


    //Method for submitting a query to a database
    /** 
     * May seem redundant, however this is necessary in order to get back the query 
     * which was passed in so properties of the query can be examined.
     * Initialises a database controller and passes the query to be executed. 
     * @param query
     * @return 
     */
    public SQLQuery registerUser(SQLQuery query){        
        DatabaseController databaseController = new DatabaseController();
        return databaseController.submitQuery(query, 0);        
    }

}
package entities;

import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import javax.xml.bind.DatatypeConverter;

/**
 *
 * @author Andrew
 * A class controlling the logic relating to the hashing of passwords.
 * 
 */
public class PasswordManager {
    String ptPassword;//Plain text 
    String hashedPassword;//Hashed
    byte[] salt;

    public PasswordManager(){
        this.ptPassword = null;
        this.hashedPassword = null;
        this.salt = null;
    }

    //Generates a salt for hashing a password
    /**
     * Uses SecureRandom and the SHA-1 Pseudo Random Number Generator to create a 
     * salt that is extremely difficult to predict.
     * Possibly a bit overkill for the security on a simple travel application, however
     * this method is good for scalability were we looking to up our game.
     * 
     * @return 
     * @throws java.security.NoSuchAlgorithmException
     */
    public byte[] generateSalt() throws NoSuchAlgorithmException{
        SecureRandom secureRandomGenerator = SecureRandom.getInstance("SHA1PRNG");
        byte[] randomBytes = new byte[128];
        secureRandomGenerator.nextBytes(randomBytes);

        return randomBytes;
    }

    //Uses the plaintext password and the salt to generate a hashed password to store in the database
    /** 
     * 
     * @param password
     * @param salt
     * @return
     * @throws Exception 
     */
    public String generateHash(String password, byte[] salt) throws Exception{
        SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
        SecretKey key = keyFactory.generateSecret(new PBEKeySpec(password.toCharArray(), salt, 2000, 128));

        String encoded = DatatypeConverter.printBase64Binary(key.getEncoded());
        byte[] decoded = DatatypeConverter.parseBase64Binary(encoded);

        String output = new String(decoded, "UTF-8");

        return output;
    } 


    //Helper method for conversion of salt to a database storable object
    /**
     * 
     * @param salt
     * @return 
     */
    public String saltToString(byte[] salt){
        return salt.toString();
    }

    //Helper method for conversion of salt to a hashable object
    /**
     * 
     * @param salt
     * @return 
     */
    public byte[] saltToBytes(String salt){
        return salt.getBytes();
    }

    public String getPtPassword() {
        return ptPassword;
    }

    public void setPtPassword(String ptPassword) {
        this.ptPassword = ptPassword;
    }

    public String getHashedPassword() {
        return hashedPassword;
    }

    public void setHashedPassword(String hashedPassword) {
        this.hashedPassword = hashedPassword;
    }

    public byte[] getSalt() {
        return salt;
    }

    public void setSalt(byte[] salt) {
        this.salt = salt;
    }


}
数据库控制器:

/*
 * TODO: Change me
 */
package entities;

import importsJSON.JSONObject;
import importsJSON.JSONParser;
import java.io.InputStream;
import java.sql.SQLException;

public class AuthenticationController {

//<editor-fold desc="Logic related to instantiation of an AC object">
/**
 * Logic related to instantiation of an AC object.
 * To instantiate an AC object, due to the method of the Singleton Pattern, 
 * AuthenticationController ac = new AuthenticationController();
 * will not work, as the constructor is private.
 * Instead calls are made as such:
 * AuthenticationController ac = AuthenticationController.getInstance();
 * 
 * This means that there will only ever be a single AuthenticationController, which
 * bears the benefit that concurrency and multiple instances are eliminated as possibilities, 
 * something arguably desirable for such functionality as login management.
 * It does need to be noted however, that this can cause a potential bottleneck and harms scalability
 * of a single server. While it is good we don't have to worry about multiple logins or 
 * "losing" a logged in user, it's also bad in the sense that we can't have multiple AC instances.
 * If 3 users require authentication simultaneously, they cannot have have their requests 
 * delegated to different AC instances to process them faster.
 * Such a system would ideally be better for scalability, but would require a great deal more work in order to ensure 
 * everything is safe and issues like concurrency are avoided.
 */
private static AuthenticationController instance = new AuthenticationController();

private AuthenticationController(){}

public static AuthenticationController getInstance(){
    return instance;
}
//</editor-fold>

public String getMessage(){
    return "HelloWorld";
}

public String authenticateUser(String rawData) throws SQLException, Exception{     
    //Variables for building a User to evaluate
    String email = "";
    PasswordManager passwordManager = new PasswordManager();
    JSONObject user = null;
    byte[] salt = null;
    int userID;

    String results ="";

    //Variables for request evaluation
    Boolean badInput = false;
    String hashedUserPassword = null;
    String storedPassword = null;
    UserModel userModel = new UserModel();


    //Convert the input to a JSON Object
    JSONParser parser = new JSONParser();
    user = (JSONObject)parser.parse(rawData);     

    //Get User Email and Password  
    try{
        email = user.get("Email").toString();
        passwordManager.setPtPassword(user.get("Password").toString()); 
    }
    catch(Exception ex){
        badInput = true;
    }

    //If the user has not provided bad data, eg null values
    if(!badInput){
        //Create SQL query to search database for a row with matching email
        //Note that we only need the ID, Password and Salt, anything else is unnecessary
        String parameterlessQuery = "Select ID, PASSWORD, PASSWORD_SALT From Users Where UPPER(EMAIL) = UPPER(?)";
        String[] params = {email};
        SQLQuery query = new SQLQuery(parameterlessQuery, params);   

        if((email!=null)&&(email.length()>0)){
            query = authenticateUser(query);
        }
        else{
            query.setErrorCode("22000");
        }

        //Retrieve variables from results list
        if(!query.getResults().isEmpty()){
            Object[] userToVerify = query.getResults().get(0);                

            //userModel.setID(userToVerify[0].toString());
            userModel.sethPassword(userToVerify[1].toString());
            userModel.setSalt(userToVerify[2].toString());

            //Use the salt and provided password to create hashed password
            String userPassword = passwordManager.getPtPassword();
            hashedUserPassword  = passwordManager.generateHash(userPassword, userModel.getSalt().getBytes());
            System.out.println("New hashed: "+ hashedUserPassword+"   Hashed: "+ userModel.gethPassword()+ "    Salt:"+userModel.getSalt());
        }
    }            

    //Finally we need to convert them 


    //Compare  hashed password from Database to newly hashed password
    return (hashedUserPassword+"   "+ userModel.gethPassword());


    //If match, user is logged in

    //If no match, user receieves a negative reply informing them of failure.

}

//Method for submitting a query to a database
/** 
 * May seem redundant, however this is necessary in order to get back the query 
 * which was passed in so properties of the query can be examined.
 * Initialises a database controller and passes the query to be executed. 
 * @param query
 * @return 
 */
public SQLQuery authenticateUser(SQLQuery query){        
    DatabaseController databaseController = new DatabaseController();
    return databaseController.submitQuery(query, 3);
}
package services;

import entities.AuthenticationController;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import javax.ejb.Stateless;
import javax.ws.rs.Consumes;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;

@Path("/Authenticate")
@Stateless
public class AuthenticationService {    

@POST
@Path("/")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public String loginUser(InputStream data) throws Exception {        
    AuthenticationController authController = AuthenticationController.getInstance();

    StringBuilder builder = new StringBuilder();

    try{
        BufferedReader in = new BufferedReader(new InputStreamReader(data));
        String line = null;
        while((line = in.readLine())!= null){
            builder.append(line);
        }
    } catch (IOException ex) {

    }
    return authController.authenticateUser(builder.toString());      
}
}
package entities;

import java.sql.*;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 *
 * @author Andrew
 * Class controlling access to databases using SQL queries.
 */

public class DatabaseController extends AbstractController{

//<editor-fold desc="Constructors and class variables">    
String targetDB;//The database it shall use to connect to
String username;//The username needed to connect to the target database
String password;//The password needed to connect to the target database

//By default, this method shall be used.
public DatabaseController(){
   this.targetDB = [redacted];
   this.username = [redacted];
   this.password = [redacted];
} 

//Should we have more than one database to manipulate.
public DatabaseController(String targetDB, String username, String password){
   this.targetDB = targetDB;
   this.username = username;
   this.password = password;
}
//</editor-fold>

//Formats and submits the query to the given database.
public SQLQuery submitQuery(SQLQuery query, int columns){        
    try {

        //Using the Oracle DB connection Driver            
        Class.forName("oracle.jdbc.driver.OracleDriver");
        //Open a connection to the target Database
        Connection connection = DriverManager.getConnection(targetDB, username, password);           

        //Build then Execute Query         
        //This may look a bit weird. Unfortunately it's the result of the nuances of the 
        //classes used, (PreparedStatement instead of Statement), however the benefit of 
        //doing so is protection from SQL injections.
        PreparedStatement statement = connection.prepareStatement(query.getParameterlessQuery());           

        if(query.getQueryParams().length!=0){
            String[] params = query.getQueryParams();

            for(int i=0; i< params.length; i++){
                statement.setString((i+1), params[i]);                   
            }
        }

        //query.setDebug(statement.toString());


        query.setResponse(statement.executeQuery());

        //If there is a result set expected, then save the results before they are lost on connection close
        while(query.getResponse().next()){                
            Object[] temp = new Object[columns];
            for (int i = 0; i < columns; i++){
                temp[i] = query.getResponse().getObject(i+1);
            }

            query.getResults().add(temp);
        }


        //Close Connection
        connection.close();
   } catch (ClassNotFoundException ex) {
        Logger.getLogger(DatabaseController.class.getName()).log(Level.SEVERE, null, ex);
        query.setDebug("driver missing");
   } catch (SQLException ex) {
        for (Throwable e : ex) {
            if (e instanceof SQLException) {                   
                query.queryFail(((SQLException)e).getSQLState(), ((SQLException)e).getMessage());

                System.out.println(query.getErrorCode() + "\n"+ query.getErrorMessage());
            }
        }
   }
    return query;
}

}
package services;

import entities.DatabaseController;
import entities.PasswordManager;
import entities.SOAPEmailValidator;
import entities.SQLQuery;
import importsJSON.JSONObject;
import importsJSON.JSONParser;
import importsJSON.ParseException;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.security.NoSuchAlgorithmException;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.ejb.Stateless;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;

/**
 *
 * @author Andrew
 * A Rather simple class that controls the registration services associated with 
 * our Web Service.
 */
@Path("/Register")
@Stateless
public class RegistrationService {    

    //Register user service
    /**
     * The default method for the /Register context of web services, meaning it is accessed
     * by a URI of form host/API/Register. 
     * Unlike most of the other methods, this is a post method and requires a sample of JSON 
     * data providing an email and password from the user in a form similar to:
     * {
     *      "Email":"[emailValue]",
     *      "Password":"[passwordValue]"
     * }
     * 
     * Everything else is then auto generated as necessary, and used to insert the user in to 
     * the database.
     * 
     * @param data
     * @return 
     */
    @POST
    @Path("/")
    @Produces(MediaType.APPLICATION_JSON)
    @Consumes(MediaType.APPLICATION_JSON)
    public Response registerUser(InputStream data){
        StringBuilder builder = new StringBuilder();

        //Variables for the building of the user to be inserted
        String email = "";
        String ptPassword = "";
        JSONObject user = null;
        PasswordManager password = new PasswordManager();
        byte[] salt = null;

        try{
            //Read in the submitted JSON
            BufferedReader in = new BufferedReader(new InputStreamReader(data));
            String line = null;
            while ((line = in.readLine()) != null) {
                builder.append(line);
            }

            //Convert the JSON String submitted by client to a JSON Object
            JSONParser p = new JSONParser();
            user = (JSONObject)p.parse(builder.toString());

            //Extract relevant values from the JSON Object
            email = user.get("Email").toString();
            ptPassword = user.get("Password").toString();

            //Generate Salt
            salt = password.generateSalt();

            //Use Salt to hash the password
            password.setHashedPassword(password.generateHash(ptPassword, salt));

        } catch (IOException ex) {
            Logger.getLogger(RegistrationService.class.getName()).log(Level.SEVERE, null, ex);        
        } catch (ParseException ex) {
            Logger.getLogger(RegistrationService.class.getName()).log(Level.SEVERE, null, ex);
        } catch (NoSuchAlgorithmException ex) {
            Logger.getLogger(RegistrationService.class.getName()).log(Level.SEVERE, null, ex);
        } catch (Exception ex) {
            Logger.getLogger(RegistrationService.class.getName()).log(Level.SEVERE, null, ex);
        }


        //Attempt to create a query
        String parameterlessQuery = "Insert into users (ID, EMAIL, PASSWORD, PASSWORD_SALT) VALUES (SEQ_PERSON_ID.nextval,?,?,?)";
        String[] params = {email, password.getHashedPassword(), password.saltToString(salt)};
        System.out.println("New Hashed:             "+"Hashed: "+ password.getHashedPassword() + "    Salt: "+ password.saltToString(salt));
        SQLQuery query = new SQLQuery(parameterlessQuery, params);

        //Check to ensure that the data is not null
        if((email!=null&&ptPassword!=null)&&(email.length()>0&&(ptPassword.length()>0))){
            query = registerUser(query);
        }else
        {
            query.setErrorCode("22000");
        }

        //If the error code suggests that the user has been created successfully, return a 200 OK message
        //to client
        if (query.getErrorCode().matches("00000")||query.getErrorCode().matches("24000")){
            return Response.status(200).entity("User Created successfully.").build();
        }
        else{//If not, return an error message with the SQLState code thrown
            return Response.status(400).entity("Failed, Error Code: "+query.getErrorCode()).build();
        }
    }


    //Method for submitting a query to a database
    /** 
     * May seem redundant, however this is necessary in order to get back the query 
     * which was passed in so properties of the query can be examined.
     * Initialises a database controller and passes the query to be executed. 
     * @param query
     * @return 
     */
    public SQLQuery registerUser(SQLQuery query){        
        DatabaseController databaseController = new DatabaseController();
        return databaseController.submitQuery(query, 0);        
    }

}
package entities;

import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import javax.xml.bind.DatatypeConverter;

/**
 *
 * @author Andrew
 * A class controlling the logic relating to the hashing of passwords.
 * 
 */
public class PasswordManager {
    String ptPassword;//Plain text 
    String hashedPassword;//Hashed
    byte[] salt;

    public PasswordManager(){
        this.ptPassword = null;
        this.hashedPassword = null;
        this.salt = null;
    }

    //Generates a salt for hashing a password
    /**
     * Uses SecureRandom and the SHA-1 Pseudo Random Number Generator to create a 
     * salt that is extremely difficult to predict.
     * Possibly a bit overkill for the security on a simple travel application, however
     * this method is good for scalability were we looking to up our game.
     * 
     * @return 
     * @throws java.security.NoSuchAlgorithmException
     */
    public byte[] generateSalt() throws NoSuchAlgorithmException{
        SecureRandom secureRandomGenerator = SecureRandom.getInstance("SHA1PRNG");
        byte[] randomBytes = new byte[128];
        secureRandomGenerator.nextBytes(randomBytes);

        return randomBytes;
    }

    //Uses the plaintext password and the salt to generate a hashed password to store in the database
    /** 
     * 
     * @param password
     * @param salt
     * @return
     * @throws Exception 
     */
    public String generateHash(String password, byte[] salt) throws Exception{
        SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
        SecretKey key = keyFactory.generateSecret(new PBEKeySpec(password.toCharArray(), salt, 2000, 128));

        String encoded = DatatypeConverter.printBase64Binary(key.getEncoded());
        byte[] decoded = DatatypeConverter.parseBase64Binary(encoded);

        String output = new String(decoded, "UTF-8");

        return output;
    } 


    //Helper method for conversion of salt to a database storable object
    /**
     * 
     * @param salt
     * @return 
     */
    public String saltToString(byte[] salt){
        return salt.toString();
    }

    //Helper method for conversion of salt to a hashable object
    /**
     * 
     * @param salt
     * @return 
     */
    public byte[] saltToBytes(String salt){
        return salt.getBytes();
    }

    public String getPtPassword() {
        return ptPassword;
    }

    public void setPtPassword(String ptPassword) {
        this.ptPassword = ptPassword;
    }

    public String getHashedPassword() {
        return hashedPassword;
    }

    public void setHashedPassword(String hashedPassword) {
        this.hashedPassword = hashedPassword;
    }

    public byte[] getSalt() {
        return salt;
    }

    public void setSalt(byte[] salt) {
        this.salt = salt;
    }


}
最后,密码管理器:

/*
 * TODO: Change me
 */
package entities;

import importsJSON.JSONObject;
import importsJSON.JSONParser;
import java.io.InputStream;
import java.sql.SQLException;

public class AuthenticationController {

//<editor-fold desc="Logic related to instantiation of an AC object">
/**
 * Logic related to instantiation of an AC object.
 * To instantiate an AC object, due to the method of the Singleton Pattern, 
 * AuthenticationController ac = new AuthenticationController();
 * will not work, as the constructor is private.
 * Instead calls are made as such:
 * AuthenticationController ac = AuthenticationController.getInstance();
 * 
 * This means that there will only ever be a single AuthenticationController, which
 * bears the benefit that concurrency and multiple instances are eliminated as possibilities, 
 * something arguably desirable for such functionality as login management.
 * It does need to be noted however, that this can cause a potential bottleneck and harms scalability
 * of a single server. While it is good we don't have to worry about multiple logins or 
 * "losing" a logged in user, it's also bad in the sense that we can't have multiple AC instances.
 * If 3 users require authentication simultaneously, they cannot have have their requests 
 * delegated to different AC instances to process them faster.
 * Such a system would ideally be better for scalability, but would require a great deal more work in order to ensure 
 * everything is safe and issues like concurrency are avoided.
 */
private static AuthenticationController instance = new AuthenticationController();

private AuthenticationController(){}

public static AuthenticationController getInstance(){
    return instance;
}
//</editor-fold>

public String getMessage(){
    return "HelloWorld";
}

public String authenticateUser(String rawData) throws SQLException, Exception{     
    //Variables for building a User to evaluate
    String email = "";
    PasswordManager passwordManager = new PasswordManager();
    JSONObject user = null;
    byte[] salt = null;
    int userID;

    String results ="";

    //Variables for request evaluation
    Boolean badInput = false;
    String hashedUserPassword = null;
    String storedPassword = null;
    UserModel userModel = new UserModel();


    //Convert the input to a JSON Object
    JSONParser parser = new JSONParser();
    user = (JSONObject)parser.parse(rawData);     

    //Get User Email and Password  
    try{
        email = user.get("Email").toString();
        passwordManager.setPtPassword(user.get("Password").toString()); 
    }
    catch(Exception ex){
        badInput = true;
    }

    //If the user has not provided bad data, eg null values
    if(!badInput){
        //Create SQL query to search database for a row with matching email
        //Note that we only need the ID, Password and Salt, anything else is unnecessary
        String parameterlessQuery = "Select ID, PASSWORD, PASSWORD_SALT From Users Where UPPER(EMAIL) = UPPER(?)";
        String[] params = {email};
        SQLQuery query = new SQLQuery(parameterlessQuery, params);   

        if((email!=null)&&(email.length()>0)){
            query = authenticateUser(query);
        }
        else{
            query.setErrorCode("22000");
        }

        //Retrieve variables from results list
        if(!query.getResults().isEmpty()){
            Object[] userToVerify = query.getResults().get(0);                

            //userModel.setID(userToVerify[0].toString());
            userModel.sethPassword(userToVerify[1].toString());
            userModel.setSalt(userToVerify[2].toString());

            //Use the salt and provided password to create hashed password
            String userPassword = passwordManager.getPtPassword();
            hashedUserPassword  = passwordManager.generateHash(userPassword, userModel.getSalt().getBytes());
            System.out.println("New hashed: "+ hashedUserPassword+"   Hashed: "+ userModel.gethPassword()+ "    Salt:"+userModel.getSalt());
        }
    }            

    //Finally we need to convert them 


    //Compare  hashed password from Database to newly hashed password
    return (hashedUserPassword+"   "+ userModel.gethPassword());


    //If match, user is logged in

    //If no match, user receieves a negative reply informing them of failure.

}

//Method for submitting a query to a database
/** 
 * May seem redundant, however this is necessary in order to get back the query 
 * which was passed in so properties of the query can be examined.
 * Initialises a database controller and passes the query to be executed. 
 * @param query
 * @return 
 */
public SQLQuery authenticateUser(SQLQuery query){        
    DatabaseController databaseController = new DatabaseController();
    return databaseController.submitQuery(query, 3);
}
package services;

import entities.AuthenticationController;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import javax.ejb.Stateless;
import javax.ws.rs.Consumes;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;

@Path("/Authenticate")
@Stateless
public class AuthenticationService {    

@POST
@Path("/")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public String loginUser(InputStream data) throws Exception {        
    AuthenticationController authController = AuthenticationController.getInstance();

    StringBuilder builder = new StringBuilder();

    try{
        BufferedReader in = new BufferedReader(new InputStreamReader(data));
        String line = null;
        while((line = in.readLine())!= null){
            builder.append(line);
        }
    } catch (IOException ex) {

    }
    return authController.authenticateUser(builder.toString());      
}
}
package entities;

import java.sql.*;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 *
 * @author Andrew
 * Class controlling access to databases using SQL queries.
 */

public class DatabaseController extends AbstractController{

//<editor-fold desc="Constructors and class variables">    
String targetDB;//The database it shall use to connect to
String username;//The username needed to connect to the target database
String password;//The password needed to connect to the target database

//By default, this method shall be used.
public DatabaseController(){
   this.targetDB = [redacted];
   this.username = [redacted];
   this.password = [redacted];
} 

//Should we have more than one database to manipulate.
public DatabaseController(String targetDB, String username, String password){
   this.targetDB = targetDB;
   this.username = username;
   this.password = password;
}
//</editor-fold>

//Formats and submits the query to the given database.
public SQLQuery submitQuery(SQLQuery query, int columns){        
    try {

        //Using the Oracle DB connection Driver            
        Class.forName("oracle.jdbc.driver.OracleDriver");
        //Open a connection to the target Database
        Connection connection = DriverManager.getConnection(targetDB, username, password);           

        //Build then Execute Query         
        //This may look a bit weird. Unfortunately it's the result of the nuances of the 
        //classes used, (PreparedStatement instead of Statement), however the benefit of 
        //doing so is protection from SQL injections.
        PreparedStatement statement = connection.prepareStatement(query.getParameterlessQuery());           

        if(query.getQueryParams().length!=0){
            String[] params = query.getQueryParams();

            for(int i=0; i< params.length; i++){
                statement.setString((i+1), params[i]);                   
            }
        }

        //query.setDebug(statement.toString());


        query.setResponse(statement.executeQuery());

        //If there is a result set expected, then save the results before they are lost on connection close
        while(query.getResponse().next()){                
            Object[] temp = new Object[columns];
            for (int i = 0; i < columns; i++){
                temp[i] = query.getResponse().getObject(i+1);
            }

            query.getResults().add(temp);
        }


        //Close Connection
        connection.close();
   } catch (ClassNotFoundException ex) {
        Logger.getLogger(DatabaseController.class.getName()).log(Level.SEVERE, null, ex);
        query.setDebug("driver missing");
   } catch (SQLException ex) {
        for (Throwable e : ex) {
            if (e instanceof SQLException) {                   
                query.queryFail(((SQLException)e).getSQLState(), ((SQLException)e).getMessage());

                System.out.println(query.getErrorCode() + "\n"+ query.getErrorMessage());
            }
        }
   }
    return query;
}

}
package services;

import entities.DatabaseController;
import entities.PasswordManager;
import entities.SOAPEmailValidator;
import entities.SQLQuery;
import importsJSON.JSONObject;
import importsJSON.JSONParser;
import importsJSON.ParseException;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.security.NoSuchAlgorithmException;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.ejb.Stateless;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;

/**
 *
 * @author Andrew
 * A Rather simple class that controls the registration services associated with 
 * our Web Service.
 */
@Path("/Register")
@Stateless
public class RegistrationService {    

    //Register user service
    /**
     * The default method for the /Register context of web services, meaning it is accessed
     * by a URI of form host/API/Register. 
     * Unlike most of the other methods, this is a post method and requires a sample of JSON 
     * data providing an email and password from the user in a form similar to:
     * {
     *      "Email":"[emailValue]",
     *      "Password":"[passwordValue]"
     * }
     * 
     * Everything else is then auto generated as necessary, and used to insert the user in to 
     * the database.
     * 
     * @param data
     * @return 
     */
    @POST
    @Path("/")
    @Produces(MediaType.APPLICATION_JSON)
    @Consumes(MediaType.APPLICATION_JSON)
    public Response registerUser(InputStream data){
        StringBuilder builder = new StringBuilder();

        //Variables for the building of the user to be inserted
        String email = "";
        String ptPassword = "";
        JSONObject user = null;
        PasswordManager password = new PasswordManager();
        byte[] salt = null;

        try{
            //Read in the submitted JSON
            BufferedReader in = new BufferedReader(new InputStreamReader(data));
            String line = null;
            while ((line = in.readLine()) != null) {
                builder.append(line);
            }

            //Convert the JSON String submitted by client to a JSON Object
            JSONParser p = new JSONParser();
            user = (JSONObject)p.parse(builder.toString());

            //Extract relevant values from the JSON Object
            email = user.get("Email").toString();
            ptPassword = user.get("Password").toString();

            //Generate Salt
            salt = password.generateSalt();

            //Use Salt to hash the password
            password.setHashedPassword(password.generateHash(ptPassword, salt));

        } catch (IOException ex) {
            Logger.getLogger(RegistrationService.class.getName()).log(Level.SEVERE, null, ex);        
        } catch (ParseException ex) {
            Logger.getLogger(RegistrationService.class.getName()).log(Level.SEVERE, null, ex);
        } catch (NoSuchAlgorithmException ex) {
            Logger.getLogger(RegistrationService.class.getName()).log(Level.SEVERE, null, ex);
        } catch (Exception ex) {
            Logger.getLogger(RegistrationService.class.getName()).log(Level.SEVERE, null, ex);
        }


        //Attempt to create a query
        String parameterlessQuery = "Insert into users (ID, EMAIL, PASSWORD, PASSWORD_SALT) VALUES (SEQ_PERSON_ID.nextval,?,?,?)";
        String[] params = {email, password.getHashedPassword(), password.saltToString(salt)};
        System.out.println("New Hashed:             "+"Hashed: "+ password.getHashedPassword() + "    Salt: "+ password.saltToString(salt));
        SQLQuery query = new SQLQuery(parameterlessQuery, params);

        //Check to ensure that the data is not null
        if((email!=null&&ptPassword!=null)&&(email.length()>0&&(ptPassword.length()>0))){
            query = registerUser(query);
        }else
        {
            query.setErrorCode("22000");
        }

        //If the error code suggests that the user has been created successfully, return a 200 OK message
        //to client
        if (query.getErrorCode().matches("00000")||query.getErrorCode().matches("24000")){
            return Response.status(200).entity("User Created successfully.").build();
        }
        else{//If not, return an error message with the SQLState code thrown
            return Response.status(400).entity("Failed, Error Code: "+query.getErrorCode()).build();
        }
    }


    //Method for submitting a query to a database
    /** 
     * May seem redundant, however this is necessary in order to get back the query 
     * which was passed in so properties of the query can be examined.
     * Initialises a database controller and passes the query to be executed. 
     * @param query
     * @return 
     */
    public SQLQuery registerUser(SQLQuery query){        
        DatabaseController databaseController = new DatabaseController();
        return databaseController.submitQuery(query, 0);        
    }

}
package entities;

import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import javax.xml.bind.DatatypeConverter;

/**
 *
 * @author Andrew
 * A class controlling the logic relating to the hashing of passwords.
 * 
 */
public class PasswordManager {
    String ptPassword;//Plain text 
    String hashedPassword;//Hashed
    byte[] salt;

    public PasswordManager(){
        this.ptPassword = null;
        this.hashedPassword = null;
        this.salt = null;
    }

    //Generates a salt for hashing a password
    /**
     * Uses SecureRandom and the SHA-1 Pseudo Random Number Generator to create a 
     * salt that is extremely difficult to predict.
     * Possibly a bit overkill for the security on a simple travel application, however
     * this method is good for scalability were we looking to up our game.
     * 
     * @return 
     * @throws java.security.NoSuchAlgorithmException
     */
    public byte[] generateSalt() throws NoSuchAlgorithmException{
        SecureRandom secureRandomGenerator = SecureRandom.getInstance("SHA1PRNG");
        byte[] randomBytes = new byte[128];
        secureRandomGenerator.nextBytes(randomBytes);

        return randomBytes;
    }

    //Uses the plaintext password and the salt to generate a hashed password to store in the database
    /** 
     * 
     * @param password
     * @param salt
     * @return
     * @throws Exception 
     */
    public String generateHash(String password, byte[] salt) throws Exception{
        SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
        SecretKey key = keyFactory.generateSecret(new PBEKeySpec(password.toCharArray(), salt, 2000, 128));

        String encoded = DatatypeConverter.printBase64Binary(key.getEncoded());
        byte[] decoded = DatatypeConverter.parseBase64Binary(encoded);

        String output = new String(decoded, "UTF-8");

        return output;
    } 


    //Helper method for conversion of salt to a database storable object
    /**
     * 
     * @param salt
     * @return 
     */
    public String saltToString(byte[] salt){
        return salt.toString();
    }

    //Helper method for conversion of salt to a hashable object
    /**
     * 
     * @param salt
     * @return 
     */
    public byte[] saltToBytes(String salt){
        return salt.getBytes();
    }

    public String getPtPassword() {
        return ptPassword;
    }

    public void setPtPassword(String ptPassword) {
        this.ptPassword = ptPassword;
    }

    public String getHashedPassword() {
        return hashedPassword;
    }

    public void setHashedPassword(String hashedPassword) {
        this.hashedPassword = hashedPassword;
    }

    public byte[] getSalt() {
        return salt;
    }

    public void setSalt(byte[] salt) {
        this.salt = salt;
    }


}

那盐
[B@681d1bc7
不是您想象的那样。看起来您保存了数组的
toString()
,而不是数组的实际内容,而且哈希中存在unicode替换字符表明您使用了错误的哈希持久化方法(或者至少,您可能想考虑使用不同的方式向我们展示它,例如hexed或based)。另请参见@markrotVeel。输出salt值的命令验证了这一点。感谢大家的提醒,我会在一切正常时发布解决方案。至于unicode字符的存在,我觉得这是因为哈希从base64编码转换为字符串以存储它。如果您对此有任何建议,我正在听。