15

My goal is to authenticate to the database using a JDBC/Hibernate in a secure manner, without storing passwords in plain text. Code examples appreciated. I'm already using waffle to authenticate the user so if there was some way to use the credentials that waffle obtained from the user, and forward those to the DB, that would be good.

Two questions:

  1. What is the recommended way to do multi hop authentication (the client, web server, and database are all different machines) with tomcat/hibernate/spring on web server, a sql database, and obviously client browser?
  2. I would also settle for a way to use a single user account to do authentication, as long as that user account's information was not stored in plain text anywhere. The user account will need both read/write privileges on the DB.

I found some useful information about connecting to SQL Server in this thread. However, I'm expecting that Tomcat will be running under the default account which is like, Local System or something. As far as I know, that account cannot be used to do windows authentication to the database.

My solution:

I did end up using the approach mentioned in the above thread. Instead of running the Tomcat service as Local System it is now running as a user. That user has permission to access the database. My hibernate configuration file is configured as follows:

    <property name="hibernate.connection.url">
jdbc:sqlserver://system:port;databaseName=myDb;integratedSecurity=true;
</property>

To those who provided responses

I appreciate everyone's help and I will try out some of the techniques mentioned in the thread. My issue with some of the responses is that they require symmetric encryption which requires a secret key. Keeping the key secret is almost the exact same problem as storing the password in plain text.

Community
  • 1
  • 1
KyleM
  • 4,445
  • 9
  • 46
  • 78

8 Answers8

14

i recently blogged about this:

you can tell tomcat's jdbcrealm to use a digest algorithm on the password like sha-256 and save the hash rather than plaintext passwords.

Suppose your User entities look like this:

@Entity
@Table(name = "cr_users")
public class UserDetails{
    @Id
    @GeneratedValue
    private long id;
    private String name;
    private String passwordHash;
    @ManyToMany
    private Set<Group> groups;
}

when creating a new User via a service it's possible to create a password hash by using a MessageDigest:

public UserDetails createNewUser(String username,String passwd,Set<Group> groups){
       UserDetails u=new UserDetails();
       u.setname(username);
       u.setGroups(groups);
       u.setPassword(createHash(passwd));
       return u;
}
public String createHash(String data){
        MessageDigest digest = MessageDigest.getInstance("SHA-256");
        digest.update(password.getBytes());
        byte byteData[] = digest.digest();
        //convert bytes to hex chars
        StringBuffer sb = new StringBuffer();
        for (int i = 0; i < byteData.length; i++) {
         sb.append(Integer.toString((byteData[i] & 0xff) + 0x100, 16).substring(1));
        }
        return sb.toString();
}

since SHA-256 will always yield the same hashvalue for the same input you can tell tomcat's JDBCRealm to use this algorithm to verify passwords.

<Realm className="org.apache.catalina.realm.JDBCRealm"
       driverName="org.postgresql.Driver"
       connectionURL="jdbc:postgresql://localhost:5432/mydb"
       connectionName="myuser" connectionPassword="mypass"
       userTable="tc_realm_users" userNameCol="username" userCredCol="passwordhash" 
       userRoleTable="tc_realm_groups" roleNameCol="groupname"
       digest="sha-256"/>

the problem is that tomcat will expect a distinct format for the usertable like this:

+----------------------+  +-------------------+
|   tc_realm_users     |  | tc_realm_groups   |
+----------------------+  +-------------------+
| username     varchar |  | username  varchar |
| passwordhash varchar |  | groupname varchar |
+----------------------+  +-------------------+

if your user data model fits you're lucky, but my Hibernate generated tables looked like that:

+----------------------+  +-------------------+  +--------------------+
|     cr_users         |  |      cr_groups    |  | cr_users_cr_groups |
+----------------------+  +-------------------+  +--------------------+
| id              long |  | id           long |  | cr_users_id   long |
| name         varchar |  | name      varchar |  | groups_id     long |
| passwordhash varchar |  +-------------------+  +--------------------+
+----------------------+

so i created a View using SQL which had the expected format and draws it's data from my webapps user data:

create view tc_realm_groups as
select 
  cr_users.name as username,
  groups.name as groupname
from cr_users 
left join (
        select 
                cr_users_cr_groups.cr_users_id,cr_groups.name 
        from cr_groups 
        left join 
                cr_users_cr_groups 
                on cr_users_cr_groups.groups_id=cr_groups.id
) as groups on groups.cr_users_id=id;

create view tc_realm_users as
select 
  name as username
from cr_users;

with that tomcat was able to authenticate/authorize agains my already existing user data and wrote the data in the context so i could use it in my Jersey (JSR-311) resources:

public Response getEvent(@Context SecurityContext sc,@PathParam("id") long id) {
                log.debug("auth: " + sc.getAuthenticationScheme());
                log.debug("user: " + sc.getUserPrincipal().getName()); // the username!
                log.debug("admin-privileges: " + sc.isUserInRole("webapp-admin"));
                return Response.ok(“auth success”).build();
        }

there are also some other Realm implementations out there:

  • JDBCRealm
  • DataSourceRealm
  • JNDIRealm
  • UserDatabaseRealm
  • MemoryRealm
  • JAASRealm
  • CombinedRealm
  • LockOutRealm

some links:

fasseg
  • 17,504
  • 8
  • 62
  • 73
  • I upvoted you because I really appreciate the effort you put into your post, but TBH, it is mostly over my head. If the pass is hashed, where is the key stored to unhash it? Where is the hash saved? What input is used to generate the hash? ... What, overall, is your reply trying to accomplish - does the user have to enter a password to authenticate against the DB? Else where is the password obtained from? Etc. Sorry but I am confused. – KyleM May 26 '11 at 20:24
  • ok i added some explanation for hashing. you dont need any keys for those kind of algorithm, they are only one way and the same input always yields the same result. hope that helped – fasseg May 27 '11 at 16:59
  • hmm now i see that i totally misread the question, i didn't understand that you just wanted a secure way to auth the webapp at the db. i thought you wanted to know how to store the webapp's user details in a secure manner IN the db. Sorry about that.. – fasseg May 27 '11 at 19:30
  • Got to love how a good answer but not a correct answer (by author's own comment) gets an upvote of 9 (at the writing time of this comment)! – Pierre May 30 '11 at 15:47
  • Ok, yeah that is why I was confused. I do/did realize that you can't unhash, what I meant to say was where does the input come from, that is used to regenerate the hash (to check the authentication creds). Either way I appreciate all your time! – KyleM May 31 '11 at 15:33
  • You are not adding a salt to the hash. This is not as secure as it should be. – Mike Pone Apr 20 '12 at 14:15
  • @Mike Pone: Thanks for the hint! With the availability of large rainbow tables or just a large enough user base unsalted hashes are not secure. But after some research it became clear that JDBCRealm has no possibility to add salt. In order to use salt a different JDBCRealm implementation has to be used: e.g. http://eneuwirt.de/2011/05/01/saltawarejdbcrealm/. I'll try to refine the answer to include salted hashes as soon as I gave it a test run. – fasseg Apr 22 '12 at 09:25
7

If I understand correctly, your environment is hibernate framework based web app deployed in tomcat.

Now currently you must have configured JDBC passsword i) either in your hibernate configuration file (generally hibernate.cfg.xml file) in property :-

hibernate.connection.password

ii) or in tomcat configuration file:-

<Resource name="jdbc/myoracle" ......password="tiger".../>

Now you wish to NOT store clear password in any of above files.

In your application code, you must be doing :-

Line1: org.hibernate.cfs.Condiguration configuration=new Configraution().configure(<hibernate configuration path>);

then,Line2:  configuration.buildSessionFactory().openSession() to create a hibernate session which has underlying JDBC connection.

1) One way can be basically:- You can have your password encrypted using any java security alogirthm using any JCE provider.You store the encrypted password in any of above configuration files (hibernate or tomcat as per your project environment).

and then between Line1 and Line2, you can have decryption logic like:-

Line1: org.hibernate.cfs.Condiguration configuration=new Configraution().configure(<hibernate configuration path>);

String encrpytedPassword=
configuration.getProperty("hibernate.connection.password"); \\will return encrypted password

//decryption logic to decypt the encrypted password:-
String decryptedPwd=decrypt(encrpytedPassword);

configuration.setProperty("hibernate.connection.password",decryptedPwd);

then,Line2:  configuration.buildSessionFactory().openSession()

You can make encryption and decryption as complex as you wish for e.g. encryption of reverse-string-of-clear-password. You can use any JCE API:- jasrypt,bouncy castle. You should need some understanding of java cryptography.Please refer to :-

http://download.oracle.com/javase/1.4.2/docs/guide/security/CryptoSpec.html

2)In case you are concerned about password being transmitting in clear in JDBC connection protocol, then you can use SSL support from DB provider to secure connection. For e.g. to have SSL JDBC connection with your DB server. Refer to your DB server resoruces for this.

EDITED TO CLARIFY keylM's comment ON HOW TO ENCRYPT THE JDBC PASSWORD

lets say you have a private and public key pair:= privare.key and public.cer. You can have JDBC password encrypted with private key and save the encrypted password in configuration file. You can use OpenSSL to import public certificate into jks (java keystore) file and have it in your JAVA_HOME\jre\lib\security.

In your decryption logic:-

  KeyStore ks = KeyStore.getInstance("JKS");
  ks.load(new FileInputStream("keystore.jks"),<jks password>); //jks password can be hardoded
Certificate cert=      ks.getCertificate(<certificate alias>);
//use certificate to decrypt the encrypted password

So in this scenario:- a hacker would need 3 things in order to capture JDBC password which makes system less vulenrable:- i) encrypted JDBC password ii) JKS store iii) JKS store password

You may ask question of then now how about JKS store password, well whether its a key,passphrase or password, in encryption-decryption system, atleast one thing shuld be highly secure; otherwise it jeopradize the whole system....in above scenario, certificate can be given to each developer machine to let him import into his jks file protected by same jks store password..everybody (developer) would know only JKS store password but never JDBC password...

ag112
  • 5,537
  • 2
  • 23
  • 42
  • @ag, @andi That link is to an enormous (and outdated?) reference. I already have an understanding of both asymmetric and symmetric cryptology. What I am trying to get is an understanding of how/what cryptology is generally used in practice. And what I don't understand about that, is how storing a decryption key in plain text, is any better than storing a password in plain text. What your code sample above doesn't mention, is that such a key would be required - data doesn't magically get decrypted. Where does the key come from? What is the best practice on managing that key? – KyleM May 27 '11 at 15:38
  • @kyleM: answered your question by editing above answer. Hope this clarifies. – ag112 May 28 '11 at 18:22
  • "You can have JDBC password encrypted with private key and save the encrypted password in configuration file." Perhaps I am wrong but I believe you meant to say ".. encrypted with PUBLIC key".. then you would decrypt it with the private key found in the JKS store. So the weakness here is that the JKS store password is stored in plain text in the code. Btw, I do agree with you that it is more secure due to more hurdles to cross to steal the password.. – KyleM May 31 '11 at 15:44
5

Normally you'd have the app authenticate to the sql database under just one user name, passing the user's details if necessary as data in its queries so that you return data pertinent to just that end user. Have your clients specified that each end user should authenticate to the database as a separate user?

Matt Moran
  • 697
  • 1
  • 9
  • 22
  • Basically my problem is this: how do we authenticate across multiple servers without hardcoding any username/password information? Authenticating under one username sounds like hardcoding the password would be necessary. But yes, using one username is fine as long as the password isn't in plain text in a file or anything like that. – KyleM Apr 22 '11 at 14:13
  • 2
    Well, typically you might have your one database user with password information stored as a hash string in a text file, but you have that text file somewhere outside the file structure made available via the web server, so it's not exposed to the outside world. When the user logs in as an app user, the app queries a table of usernames on the database to see if s/he's a registered user. The app user doesn't need to know about the DB user credentials, only the app user credentials. If you're using Spring, this might be a useful doc to read: http://s.coop/15r7 – Matt Moran Apr 26 '11 at 23:25
  • then the problem would still remain, how would I use the hash string to authenticate against a SQL Server database. In theory your approach is sound but it is the implementation where I'm stuck. Thanks again. – KyleM May 23 '11 at 16:18
2

To be able to transparently encrypt/decrypt passwords in your database with hibernate you need to integrate something like Jasypt.

Homepage: www.jasypt.org See section: Jasypt + Hibernate 3

Here is how to integrate it:

  1. Download jasypt.jar and add it to your runtime classpath

  2. I would suggest using a registered encryptor:

<typedef name="encrypted" class="org.jasypt.hibernate.type.EncryptedStringType">
  <param name="encryptorRegisteredName">strongHibernateStringEncryptor</param>
</typedef>
<class name="User" table="USER">
  <property name="password" column="PASSWORD" type="encrypted" />
<class>

andi
  • 981
  • 2
  • 11
  • 18
2

Okay, let's take a look at the problem. You want to have the authentication information made available but not hardcoded anywhere in code or in file system. What I would suggest:

  • require the administrator of the application to specify the authentication information upon application startup either via jmx or via a webpage that does not require any database connection.
  • Add a servlet filter to limit access until database authentication information is entered.

This solution does require some extending spring context loading so that it waits until the authentication information is specified (via entry page).

Pierre
  • 1,329
  • 2
  • 12
  • 21
  • 3
    Even though this is seems a little silly ... it is the only real answer to the question – sMoZely May 27 '11 at 20:18
  • thank you. I've actually implemented a similar solution for a project that required high level of security and no password was to be hardcode in any configuration file. – Pierre May 30 '11 at 15:41
1

Usually, this is handled using a "sysadmin" approach - using the OS:

The basic concept is "externalising configuration parameters". Passwords are stored in plaintext in a "properties file" (that the web server accesses at runtime). The passwords are protected by restricting access to the files using OS-level file permissions. Typically, only "operations" staff can read/write the file, and the web server needs to run with read-only privileges to the file.

The benefits of this approach are:

  • Simple to understand and implement (no entering encrypted values)
  • Protected by software designed to protect - it's one of the few things that an OS does (also, encryption can be cracked if the file can be read)
  • Simple to set up dev/test environments - just open up permissions for dev/test. Also, only the production runtime server needs to have proper security
  • Avoids dependencies on "no business value" libraries (that don't help solve your business problem)
Bohemian
  • 412,405
  • 93
  • 575
  • 722
  • "However, I'm expecting that Tomcat will be running under the default account which is like, Local System or something. As far as I know, that account cannot be used to do windows authentication to the database." So are you suggesting that I run the Tomcat service with a different account than local system? Also, this doesn't answer the original question, which mentioned no plain text... but thank you for your input. – KyleM May 24 '11 at 14:22
  • Running Tomcat on another service (not local system) is generally a good idea, regardless. – Will Iverson May 24 '11 at 21:02
1

You can use a JDNI Datasource on your application server that will have the connection information for the Database.

Then you can just tell your application via your web.xml to use the datasource on your web application server.

Here is how I did it on a Weblogic 9 using Hibernate 3:

In Hibernate.cfg.xml

<property name="connection.datasource">jdbc/MYJDNINAME</property>  
<property name="connection.autocommit">true</property> 
<property name="hibernate.connection.release_mode">on_close</property>

In weblogic.xml

    <reference-descriptor>
    <resource-description>
        <res-ref-name>jdbc/MYJDNINAME</res-ref-name>
        <jndi-name>MYJDNINAME</jndi-name>
    </resource-description>
</reference-descriptor>

Similar solutions can be used for tomcat and other application servers:

Tomcat 6 Instructions

bsimic
  • 926
  • 7
  • 15
  • Can you please elaborate on your above post? For example, how would JDNI help me to connect without hardcoding information (it doesn't seem like that's what its for..)? Where would it get authentication info from? What would it do to use that authentication info? I am new to JDNI so an explanation of why I should use JDNI to solve this issue would help. Thanks for answering! – KyleM May 24 '11 at 15:01
1

I got your point KyleM. You can do :

  1. Create plain text file or registry(in case of Windows) place somewhere on other server in encrypted mode.

  2. OR you can use this Lamport's one-time password algorithm

Kamahire
  • 2,149
  • 3
  • 21
  • 50
  • Lamport's algorithm verifies that you're talking to the same person. It doesn't verify that the person you're talking to is trusted. You have to make that decision separately, before using the algorithm. So how is Lamport's algorithm applicable here? – KyleM May 25 '11 at 14:05
  • It will avoid hard coding of password. – Kamahire May 25 '11 at 14:21