• 0

Proper way to hash passwords in PHP?


Question

I recently read an article on Ars Technica, and it recommended using SHA512crypt to hash passwords (instead of 'weaker' methods like MD5 or SHA1).

Is that the best way to hash passwords? Is there an easy way to do this in PHP?

Link to comment
https://www.neowin.net/forum/topic/1101713-proper-way-to-hash-passwords-in-php/
Share on other sites

Recommended Posts

  • 0

I use this (my salt is random, and each user has their own. It's stored in the user's SQL row):

$temp_salt = "DEADBEEF";
$temp_pass = "PaSSWorD1";
$hash_method = "sha256"; // Could also use sha1, sha512 etc, etc

$pass = hash($hash_method, hash($hash_method, $temp_pass) . hash($hash_method, $temp_salt));

(edit: updated code :p)

  • 0

I find that bcrypt article highly suspicious, 'using salts doesn`t help', first thing on the example, using a salt, so the article doesn't appear to be all that great.

And seeing as http://yorickpeterse...use-bcrypt-fool shows to use crypt() in PHP, there you have it, rubbish implementation and ignore, use something that's actually worthwhile.

  • 0

I'm not a PHP developer, but I have made many systems that store passwords. If crypt() is using bcrypt, then that is great news - I'm sure most PHP devs just use crypt() because of the name alone. I did just look at the php docs: http://nl3.php.net/manual/en/function.crypt.php and it appears that this does not use bcrypt. The method also does not allow you to specify an iteration count, so I'm not exactly sure you are right.

The OP asked if he should use SHA512 or what to hash passwords. The answer is, use bcrypt. The point of the article is (not sure if you read the whole thing), SHA/MD5 hashes are very very quick to compute. Modern setups can crack hashed and salted passwords with a brute force mechanism in no time at all. This is because hashing functions are designed to be fast. Bcrypt allows you to specify a parameter which specifies iterations, which a higher number makes the hashing much slower. Introducing bcrypt will make brute forcing one password take years or more.

  • 0

that bcrypt article says salt won't help but I disagree. It is something extra you can do which provides security against previously cracked passwords.

We all know there are sites on the internet where you can punch in an MD5 hash and the site will eventually crack it after your submission reaches the top of the queue. All the MD5's cracked in this way are then displayed on the site forever so that passwords that are submitted multiple times aren't being cracked multiple times.

If you add salt to your passwords you block this because your MD5 won't match the others even if your users use the same password. You can even take this an extra step further by giving every hash a unique password by using the IP Address or other unique information of the user who is using your service.

Now yes obviously if the attacker gets a hold of your salt they can combine that with their own cracker to crack your passwords but again that isn't what the salt protects against and I would still recommend doing it considering that it would take you 5-10 minutes to implement and raises the security of all your users in the eventuality that your database is taken, it buys them time to go about changing their passwords on other sites (and we all know users use the same password on multiple sites). Force database stealers to crack your passwords themselves instead of just comparing your database to publicly available tables of already cracked passwords.

Personally I use SHA512. I don't think bcrypt is necessary but that's just my opinion, obviously it takes longer to crack a bcrypt hash so if you want the best that is what you should use.

  • 0

My experiance of using crypt(), even with forced runs being 1, it does not generate a valid hash that can be created using it's real-world alternative, such as MD5, so you're stuck with only using it in PHP.

Plus it only gave 10 characters or so, missing loads of characters off.

  • 0

Here are some examples:

Login.php


<?php
include 'db.php';

//Start the session
session_start();
echo $_SESSION['username'];
define('MAX_SALT_LENGTH', 32);
mysql_connect(SERVER, USERNAME, PASSWORD) or die(mysql_error());
mysql_select_db(DBNAME) or die(mysql_error());
$user = mysql_real_escape_string($_REQUEST['username']);
$password = mysql_real_escape_string($_REQUEST['password']);
function generateHash($salt, $password) {
$hash = hash('sha256', $salt . $password);
return substr($hash, 0, MAX_SALT_LENGTH);
}
//$_REQUEST used because this value wasn't passed via the login form
$pageLocation = $_REQUEST['page'];
$result = mysql_query("SELECT * FROM users WHERE username = '$user'") or die(mysql_error());
if(mysql_num_rows($result) != 0)
{
$row = mysql_fetch_array($result) or die(mysql_error());
$salt = $row['salt'];
$hashedPassword = generateHash($salt, $password);
if($row['password'] == $hashedPassword)
{
//Set the username for the current session
$_SESSION['username'] = $row['username'];
echo "Welcome, " . $row['username'] . "!";
echo "<br /><a href='./scripts/php/logout.php'>Logout</a
}
else
{
echo "<p>Login Failed!</p>";
}
}
else
{
echo "<p>User Not Found!</p>";
}
?>
[/CODE]

Register.php

[CODE]
<?php
include 'db.php';

define('MAX_SALT_LENGTH', 32);
$username = $_REQUEST['registrationUsername'];
$password = $_REQUEST['registrationPassword'];
mysql_connect(SERVER, USERNAME, PASSWORD, DBNAME) or die(mysql_error());

mysql_select_db(DBNAME) or die(mysql_error());
function createSalt() {
$string = hash('sha256', uniqid(rand(), true));
return substr($string, 0, MAX_SALT_LENGTH);
}

$salt = createSalt();

function generateHash($salt, $password) {
$hash = hash('sha256', $salt . $password);
return substr($hash, 0, MAX_SALT_LENGTH);
}

$fullHash = generateHash($salt, $password);
$result = mysql_query("INSERT INTO users (username, password, salt) VALUES ('$username', '$fullHash', '$salt')") or die(mysql_error());
?>
[/CODE]

  • 0

I generally use something like


$passHash = sha1($_REQUEST['password']); //you could also do the hashing on the client side, which would protect the password from passive network attacks
$globalSalt = "super_salt";
$custSalt = substr($passHash, 5, 20); //since SHA1 hashes are 40 characters long, you could make the start & length any where in that range
$realHash = hash("sha256", $passHash . $globalSalt . $custSalt); //or, instead of hash("sha256"), you could use bcrypt or phpass or some 3rd party thing
[/CODE]

with a custom

[CODE]$globalSalt[/CODE]

and start & length parameters for

[CODE]substr()[/CODE]

it's not going to protect against someone who's really determined to break the hash (especially if the code is open-source and they know you're doing it like this), but in a worst-case scenario they'll still have to go through like 40^2 (probably less, but that's just off the top of my head) different start & length possibilities, and then try to guess the global salt along with each of those.

  • 0
  On 28/08/2012 at 22:29, Xerax said:

I use this (my salt is random, and each user has their own. It's stored in the user's SQL row):

$temp_salt = "DEADBEEF";
$temp_pass = "PaSSWorD1";
$hash_method = "sha256"; // Could also use sha1, sha512 etc, etc

$pass = hash($hash_method, hash($hash_method, $temp_pass) . hash($hash_method, $temp_salt));

(edit: updated code :p)

your salt and password combo needs to be hashed together, not just concatnated

  • 0
  On 28/08/2012 at 22:29, Xerax said:

I use this (my salt is random, and each user has their own. It's stored in the user's SQL row):

$temp_salt = "DEADBEEF";
$temp_pass = "PaSSWorD1";
$hash_method = "sha256"; // Could also use sha1, sha512 etc, etc

$pass = hash($hash_method, hash($hash_method, $temp_pass) . hash($hash_method, $temp_salt));

(edit: updated code :p)

I'm not a security/hashing expert or anything, so excuse me if i'm horribly wrong, but if someone gets access to the database then wouldn't they also have access to the salts? so wouldn't that defeat the purpose of the salt in the first place?

just asking here because I've seen this suggested on other sites, and have always wondered about it O.O

  • 0

I wrote a 50 line PHP class that I use to generate hashes of info. It takes in a string and hashes it 15 times, using SHA512, SHA1 and MD5. Also in the next to last of those 15 steps I add on some characters to extend the input string out to 128 bytes. Then when that is done the string is broken down in to 4 32 byte pieces and a pseudo-random one (that is then SHA512 hashed a 16th time) is chosen as a key to encrypting the entire hash using AES256. Probably a bit overkill but eh, I don't care. It was fun to write, and I had to actually restrain myself from adding more hashing.

I use this class in one app to encrypt both usernames and passwords.

Also none of this uses bcrypt. Though I might tweak it to do so later on.

  • 0
  On 29/08/2012 at 02:43, Matthew_Thepc said:

I'm not a security/hashing expert or anything, so excuse me if i'm horribly wrong, but if someone gets access to the database then wouldn't they also have access to the salts? so wouldn't that defeat the purpose of the salt in the first place?

just asking here because I've seen this suggested on other sites, and have always wondered about it O.O

Yeah, if you store the salt alongside the password you're kinda defeating it (If they get a dump of the DB they have each users salt, so while it defeats a rainbow table attack it doesn't stop brute forcing)

If you store the salts or such on the file system in an area outside of where the web server can access, they an attacker can't gain access to the salts via an SQL exploit (also, don't use the mysql_* APIs) or via directory browsing, etc. (they need shell access, so you don't want the ability for users to upload random data that could be executed, like uploading a .php file in an image and tricking the server into executing it, etc.)

  • 0

The purpose of a salt is to defeat pre-computing a list of all hashes for common passwords.

If you just hash(password) then I can generate a list of every possible password and what they hash into well in advance. Even if I spent a year pre-computing my database - it would allow me to break lots of passwords in no time at all. Better still - many sites use similar hashing algorithms and many sites don't salt so I can compute once and recycle that database on many sites.

If you salt your database I can no-longer pre-compute it - i need to get your database and rebuild my key->hash database for every single salt. If it takes a year to build that database then even a site with only 50 users will take until I die to completely compromise (provided they don't recycle salts).

Because exhaustively searching the key space for a particular hashing function is time prohibitive (millions of years for good algorithms) it forces me to be more targeted with my attack. I can only afford to generate that data with a smaller set of input keys because I need to not take forever.??I can generate salt+key for say 200 million keys but that would force me to limit my search to common passwords, dictionary words with common substitutions, etc.

Further it protects users with the same password.

Suppose you and I both choose "password" as our password: they'll both hash to the same thing because that's what hashes do (map exactly one input to --ideally-- exactly one output). If the attacker lucks out an cracks my hash then can then look through the database for matching hashes and find all of the other users that had the same password. If all the keys are salted they'll still need to crack each user's hash one at a time because the hash stored isn't hash(mypassword)+salt, but hash(mypassword+salt).??Even if you know the salt that's still only part of the puzzle and not enough to predict the entire hash.

  • 0

Am I the only one seeing people double-hashing stuff and thinking they're just adding collisions to their hashes?

Hash functions are meant to be applied once. If you need a hash function that takes longer to compute or offers less possible collisions, use a different hashing algorithm. Hashing algorithms are built to have the least amount of collisions possible (which is how you avoid rainbow tables). hash(hash(something)) only makes you more vulnerable to collisions.

Pick your hashing algorithm, concatenate your salt+password and has that. Keep your salt safe somewhere else if possible.

  • 0
  On 29/08/2012 at 03:34, The_Decryptor said:

Yeah, if you store the salt alongside the password you're kinda defeating it (If they get a dump of the DB they have each users salt, so while it defeats a rainbow table attack it doesn't stop brute forcing)

Actually no, that's wrong. Adding a salt means all pre-existing SHA/MD5 wordlists are obsolete because they don't factor in a salt. (The reason for the salt.)

  On 29/08/2012 at 04:09, Menge said:

Am I the only one seeing people double-hashing stuff and thinking they're just adding collisions to their hashes?

Hash functions are meant to be applied once. If you need a hash function that takes longer to compute or offers less possible collisions, use a different hashing algorithm. Hashing algorithms are built to have the least amount of collisions possible (which is how you avoid rainbow tables). hash(hash(something)) only makes you more vulnerable to collisions.

Pick your hashing algorithm, concatenate your salt+password and has that. Keep your salt safe somewhere else if possible.

Weird, IP.Board uses the method I posted. I just assumed it was a good method. But I see what you mean.

  • 0

Let's get something straight, Rainbow tables VS brute force;

Rainbow tables are available in various sizes, e.g. 1-8 characters in length, 8-12, etc. there's a set of free 1-8 character MD5 password hashes, from what I remember the entire archive is 250GB or so, and it took YEARS on multiple clusters to generate it. It's very fast (if you have lots of RAM) to find unsalted password hashes.

Brute force, on the other hand, isn't even worth doing on a CPU if the password has a salt or if the password is long, will it take years? Maybe. Will it take 10s of years? Probably. Using a fast supported GPU (or multiple) will greatly speed up the process, but again, if you do multiple hashes and tricks like, having the user's password first character followed by the salt followed by the rest of the password or random positions, you'll find it's much much harder to crack it. If you MD5 something 3 times and the hacker wants the cleartext password (unless you're a bank, most hackers aren't interested in getting a random user account), they'd have to crack 2 16-character hashes and then the original password... Even for MD5, that's a possible 26^16 passwords, 4.36087429?10²², per rehash.

So yeah, good luck cracking that.

  • 0
  Quote
Brute force, on the other hand, isn't even worth doing on a CPU if the password has a salt or if the password is long, will it take years?

That's a non-sequitor: nobody bothers trying to brute force the entire key space when you get get 20% of the result for 1-trillionth the effort by running a dictionary of common english words + common substitutions + common passwords. Some users have strong passwords, most don't.

I don't fault you for not having studied cryptography or complexity theory in college, but to make assertions like this in a technical thread after others have pointed out exactly the way in which you're wrong is pretty ridiculous.

  Quote
if you do multiple hashes

This does nothing to help and may do things to harm by reducing the domain over which the hashing function operates.

  Quote
and tricks like, having the user's password first character followed by the salt followed by the rest of the password or random positions, you'll find it's much much harder to crack it.

None of these tricks does anything to increase the domain over which the hashing function must operate nor the complexity of the hashing function, nor the range of possible output values. None of them adds new information to be considered so the end result is absolutely no change in the difficulty of finding an input that will produce a given output for the hash function(s) in question.

  • 0
  On 29/08/2012 at 15:02, evn. said:

I don't fault you for not having studied cryptography or complexity theory in college, but to make assertions like this in a technical thread after others have pointed out exactly the way in which you're wrong is pretty ridiculous.

None of these tricks does anything to increase the domain over which the hashing function must operate nor the complexity of the hashing function, nor the range of possible output values. None of them adds new information to be considered so the end result is absolutely no change in the difficulty of finding an input that will produce a given output for the hash function(s) in question.

Wow, big-headed and retard, great work skills...

If they wanted to gain access to your account and had your MD5, then they'd have access to just log themselves in on the security system itself wouldn't they, so getting the password is the first and foremost thing they want, so doing MD5() or whatnot multiple times DOES increase security, honestly if you can't see it, you shouldn't ever be working on computers or computer-based systems.

No change?

Let's do some testing shall we? Go and time how long each of the following takes via brute force assuming you know the salt but you don't know the password and you've decided to use a mask brute force;

MD5('passw0rd')

MD5(MD5('passw0rd'))

MD5('p' + 's4l7' + 'assw0rd')

MD5('s4l7' + 'passw0rd')

MD5('pass' + 's4l7' + 'w0rd') {50% of the string length, then salt, then remaining 50%}

MD5(MD5('p' + 's4l7' + 'assw0rd'))

MD5(MD5('s4l7' + 'passw0rd'))

MD5(first_8_characters(MD5('pass' + 's4l7' + 'w0rd')) + 's4l7' + last_8_characters(MD5('pass' + 's4l7' + 'w0rd')))

When you've done all of that and proven your point (which won't happen because your point is wrong) I'll gladfully agree that you're right.

Or chicken out of it because you really haven't got a clue...

The choice is yours.

  • 0
  Quote
If they wanted to gain access to your account and had your MD5, then they'd have access to just log themselves in on the security system itself wouldn't they, so getting the password is the first and foremost thing they want,

The reasons passwords are hashed is not to prevent an attacker from gaining access to a particular users account on the compromised site, but to ensure they can't use information gleaned from compromising this site to gain access to another. Password hashing is something done to protect the user not the site administrator.

  Quote
Let's do some testing shall we? Go and time how long each of the following takes via brute force assuming you know the salt but you don't know the password and you've decided to use a mask brute force;

Your phrasing is a bit suspect but I'll try and walk you through this inspite of the petty name calling.

The complexity of 'guessing' an input that yields a particular output for any given function is proportional to the domain of the function.??The more possible inputs you have to consider the harder it is to guess.??

Due to systemic weakness in MD5 the maximum??key space is practically treated as 123 bits long though in an ideal world it would have be 128 bits.

In the case of hashing passwords we can restrict the domain a bit further. There are only certain inputs - namely those inputs that human beings can reasonably be expected to type (you're not going to be using "\ESCAPE \LF \BELL hello \CR " as a password). For the sorts of keys you can realistically expect a person to use (letters, numbers, and punctuation) you get around 6 bits of entropy per character. If I assume things like common english words with letters/numbers substituted etc.??then the entropy per character can be cut in half.

Lets say I want to crack your passwords and I plan to check every combination letter/number/punctuation up to 16 characters long - that's about 96 bits of entropy to worry about. It clearly defines the key space I'm working with. The only way to increase the key space is to force me to consider more complex passwords which realistically means longer passwords.

Still 96 bits is a pretty respectable search space - I wouldn't want to be responsible for searching through it on a deadline. Where you've gone wrong is in assuming you can somehow increase the entropy in the password (and there-by increase the key space) by some bit-twiddling once you have the password. Entropy needs to come from outside the system - i needs to come from the user - that's just the nature of the universe. The additional data needs to be 'random' (or more precisely, unpredictable) otherwise it has no impact on key space. You can think of it like adding a constant to a function: it just shifts the domain/range of the function but doesn't change the area market out on a graph.

Lets go over your examples, look at the bits of entropy and see what they tell you about the key space.

  • MD5('passw0rd') => You've got an 8 character password but I'm going to assume I check up to 16 characters - that's 96 bits of entropy in the key space.
  • MD5(MD5('passw0rd')) => the key space for the outer function is drastically compressed - I know it will always see exactly 32 A-Z0-9 characters because the inner function always returns a 16-byte number.??Still, the reduced key space you've created is going to be larger than the 96-bits in a 16-character password.??This one has a keyspace identical to the first.
  • MD5('p' + 's4l7' + 'assw0rd') => I know the salt and where it falls when you bit-twiddle - that doesn't add entropy to the system because the information is entirely predictable.??The key space for passwords hasn't changed so it's computationally identical to the first.
  • MD5('pass' + 's4l7' + 'w0rd') The entropy per character takes into account string length (it's a function of it, by definition).??Here you're still only using entropy in the password itself - which is about 96 bits. the search space is the same as the above.
  • MD5(MD5('p' + 's4l7' + 'assw0rd')) => once again you're telling me where the splits are, you're telling me the salt. You're telling me how to shuffle them I just need to find the bits of information I don't have. The information I don't have is the users password so that means searching through that same 96-bit key space.
  • MD5(MD5('s4l7' + 'passw0rd')) we did this already - the only information you haven't told me is the bits provided by the user. The search space is the same as the first example because in both cases I'm still only missing the user's password information.
  • MD5(first_8_characters(MD5('pass' + 's4l7' + 'w0rd')) + 's4l7' + last_8_characters(MD5('pass' + 's4l7' + 'w0rd'))).??Recyling bits doesn't add entropy.??You're telling me the salts, I know all the manipulations to do. The only thing I'm missing is the bits of information provided by the user. The search space is the same as the above.

You seem to think there's some way of extracting "extra entropy" by bit twiddling but that just isn't the case. The only way to increase the difficulty of cracking an md5 is to increase the information the attacker doesn't know up to the limits of the key space.

Stronger hashing algorithms are better than MD5 because they increase the search space by increasing the range of output values. A larger range of output values necessarily maps a larger range of input values and increases the space I need to consider when attempting to exhaustively search for collisions.??My argument has never been that MD5 isn't suitable for storing passwords -- given the restrictions on the problem set it's being applied to (things people type at a keyboard) it's almost certainly a 1:1 mapping over the domain it's being used for.

My argument is that you can't meaningfully "add security" (that is: increase the search space) by twiddling the characters of a password and then telling me what twiddling you did. You need more unpredictable data to increase the search space and that's not something you can just calculate.

The wikipedia article on Information theory kinda covers it but it's not really a good resource for somebody that isn't already familiar with the topic. The key idea here is that for communication to take place -- for information to be transferred -- the information that is sent must not be known in advance. If you have a "hunch" about the value (ie: 70% of the time a particular bit is likely to be 1 instead of 0) then it's possible you're transferring less than 1 bit of information at a time.??In the case of an attacker with access to your site you have to subtract anything that they can know for certain in advance:

  • Salts and constants you append
  • algorithms
  • whatever bit-twiddling trickery you do
  • assumptions you make or enforce about input length/size/character set, etc.

The only thing that the attacker doesn't know in advance is the input value to generate the hash so that's the only thing that is actually being communicated.??We know that all possible messages that map to a particular hash value exist in some particular search space (96 bits long in our example) and so that's really all there is to work out.

Maybe you think that you can add computational complexity by making a computer run more calculations.??If MD5 takes 1 second to run then you can do x md5(md5(md5(md5....1 million times)??and make a password unhackable because it takes a 6 weeks to check each one.??There are practical limits to that (it opens the door to being DOS attacked just by having somoene log in) but it also ignores that fact that when you compose F(x), G(x), H(x)... to end up with F(G(H(x))) you can just re-write that as (FoGoH)(x) and that is going to have some function J(x) that is analogous.??J(x) need not be identical with (FoGoH)(x), it just needs to be consistent over the domain of values we are considering.??The allows an attacker to simplify his function or to optimize it such that it may be quicker for the attacker to calculate hashes than you can.??Imagine the functions F(x) =x*x +4, G(x) = sqrt(x-4)+pi, H(x) = x -cos^-1(-2). vs their simplified composition for a contrived example.

I hope now you can understand why repeatedly running a md5 on a given input, or diddling the bits of the input in a predictable way, does nothing meaningful to increase the cryptography security of your password storage. Brute force attacks are chiefly limited by the size of the search space which is a function of the entropy in the password.

This topic is now closed to further replies.
  • Posts

    • Brave... the crypto browser with Brave ad rewards and Leo AI everywhere is not an issue for you and Firefox is? At least in Firefox you can disable everything you don't want easily in about:config and everything you don't want is REALLY removed even from the UI. in Brave the only way to really disable all this stuff and them to completely removed from the UI is to use administrator policies. You can only have them turned off without admin policies.
    • The UK shouldn't copy Trump. If the UK wants its own AI Industry it needs to build one, it also need to sort out the issue of startups flying away to America.
    • Azure Linux 2.0 reaches end of life, requiring AKS Arc users to upgrade by Paul Hill Microsoft has warned that Azure Linux 2.0, used in Azure Kubernetes Service (AKS) enabled by Azure Arc, will reach its end of life on July 31, 2025, necessitating users to upgrade. After this date, Microsoft will no longer provide updates, security patches, or support for Azure Linux 2.0. The longer it is used after this date, the more vulnerable systems will become due to a lack of patches. Azure Linux 3.0 brings significant upgrades to core components that enhance performance, security, and the developer experience. The Linux kernel is upgraded from version 5.15 to 6.6, bringing performance and hardware compatibility improvements. The Containerd package has been updated from version 1.6.26 to 1.7.13, improving container management. The SystemD package has been upgraded from version 250 to 255, streamlining system and service management, and OpenSSL has jumped from version 1.1.1k to 3.3.0, providing enhanced encryption and security. Azure Linux 3.0 also brings more packages and better tooling. Major versions of Azure Linux are typically supported for three years, with Azure Linux 3.0’s EOL projected for Summer 2027. It became generally available in August 2024.Microsoft said that users must migrate to Azure Linux 3.0 by upgrading their Azure Local instances to the 2507 release when it becomes available. After the Azure Local instance has been upgraded, users can then upgrade their Kubernetes clusters. Microsoft gives you the option to remain on the same Kubernetes version during this upgrade by providing the same version number on the aksarc upgrade command. After upgrading, you can verify the kernel version on your Linux nodes by adjusting the file path in this command: kubectl --kubeconfig /path/to/aks-cluster-kubeconfig get nodes -o wide This upgrade is mandatory for continued support. If you want to learn more, check out Microsoft’s announcement which also includes how to reach out to the AKS Arc team if you need to.
    • PDF-XChange Editor 10.6.1.397 by Razvan Serea PDF-XChange Editor is a comprehensive PDF editor that allows you to create, view, edit, annotate, and digitally sign PDF documents with ease. With advanced features like OCR, document security, and PDF optimization, PDF-XChange Editor is a powerful tool for both personal and professional use. Whether you need to edit text, images, or links, or add comments, stamps, or watermarks, PDF-XChange Editor provides all the necessary tools to make your PDFs look perfect. Additionally, it supports a wide range of file formats, including PDF, XPS, and DOCX, making it easy to convert and share your documents. PDF-XChange Editor key features: Edit text and images in PDF documents Add and remove pages from PDF files Annotate and markup PDFs with comments, highlights, and stamps Use OCR to convert scanned documents into searchable text Create and fill out PDF forms Sign and certify PDF documents digitally Add and edit hyperlinks within PDFs Extract text and images from PDF files Batch process multiple PDF files at once Customize the interface to your preferences Work with multiple documents in tabs Convert PDFs to other formats such as Word, Excel, and HTML Use advanced redaction tools to permanently remove sensitive information Add customizable headers and footers to PDFs Merge multiple PDF documents into a single file Split PDF documents into multiple files Add watermarks to PDF documents Use the measurement tools to calculate distances and areas in PDFs ....and much more PDF-XChange Editor 10.6.1.397 changelog: New Features Added offline support for MIP-protected documents. Bug Fixes & Improvements Fixed some security and stability issues. Click here for further information. Fixed an issue with DocuSign when a document name is very long. Fixed a very rare issue when opening password-protected files. (46285) Fixed an issue with moving content items in some cases, where arrow keys became unexpectedly deselected under certain conditions. (T# 7476) (40279) Fixed an issue with disabling the Zoom In/Out tool after using the Snapshot tool. (46330) Fixed an issue with an unintentional button press when the Autoscroll command was executed under specific conditions. (46289) Fixed an issue with resetting object selections when panning with mouse middle button. (46358) Fixed an issue with adding a highlight when starting to drag directly above the comment that is itself above base content text. (46410) Fixed an issue with adding a guideline by dragging from the ruler while a text comment or base content is being edited. (45858) Fix the call to app.clearTimeOut after the timeout handler was executed and the associated timer was unregistered. Improved static XFA support. Fixed an issue when adding Insert/Replace text markup comments to the first word in a text line. (47120) For a complete list of changes and more detailed information, please refer to the official PDF-XChange Editor Version History. Download: PDF-XChange Editor (64-bit) | Portable ~300.0 MB (Shareware) Download: PDF-XChange Editor (32-bit) | Portable ~200.0 MB Download: PDF-XChange ARM64 | 264.0 MB Download: PDF-XChange Portable @PortableApps.com | 97.0 MB View: PDF-XChange Editor Website | Screenshot Get alerted to all of our Software updates on Twitter at @NeowinSoftware
  • Recent Achievements

    • Dedicated
      Daniel Pinto earned a badge
      Dedicated
    • Explorer
      DougQuaid went up a rank
      Explorer
    • One Month Later
      MIghty Haul earned a badge
      One Month Later
    • Week One Done
      MIghty Haul earned a badge
      Week One Done
    • Collaborator
      KD2004 earned a badge
      Collaborator
  • Popular Contributors

    1. 1
      +primortal
      597
    2. 2
      Michael Scrip
      201
    3. 3
      ATLien_0
      192
    4. 4
      +FloatingFatMan
      140
    5. 5
      Xenon
      127
  • Tell a friend

    Love Neowin? Tell a friend!