Froome tried to attack but the Spaniard tracked him up the final climb and overtook him in the last 500m.
Tinkoff-Saxo's Contador, who won his home Grand Tour in 2008 and 2012, now has a 1:37 lead going into Sunday's final stage - a 9.7km time trial.
Team Sky's Froome is 58 seconds ahead of third-placed Alejandro Valverde.
Froome tweeted after the stage: "Emptied the tank out there today, what a battle! Thank you for all the support."
The leading general classification riders had sat in the peloton for most of the 185.7km stage from Santo Estevo de Ribas de Sil to Puerto de Ancares.
A four-man breakaway involving Przemyslaw Niemiec, Jerome Coppel, Maxime Mederel and Wouter Poels had reached a 10-minute advantage at one point, but the peloton reeled them in just before the final climb - the brutal 12km category one Puerto de Ancares.
It left the top five general classification riders battling it out and, after Joaquim Rodriguez's attack had been reeled in, Froome pressed on.
The 2013 Tour de France champion accelerated and managed to shake off the others but could not remove Contador from his back wheel.
He bided his time and struck in the final kilometre to take his second stage win of the 2014 race by 16 seconds.
Stage 20 result:
1. Alberto Contador (Spain / Tinkoff - Saxo) 5:11:43"
2. Chris Froome (Britain / Team Sky) +16"
3. Alejandro Valverde (Spain / Movistar) +57"
4. Joaquim Rodriguez (Spain / Katusha) +1:18"
5. Fabio Aru (Italy / Astana) +1:21"
6. Warren Barguil (France / Giant) +2:51"
7. Giampaolo Caruso (Italy / Katusha) +2:55"
8. Samuel Sanchez (Spain / BMC Racing) +2:58"
9. Daniel Navarro (Spain / Cofidis) +3:15"
10. Damiano Caruso (Italy / Cannondale) +3:20"
Overall classification after stage 20:
1. Alberto Contador (Spain / Tinkoff - Saxo) 81:12:13"
Chris Froome (Britain / Team Sky) +1:37"
3. Alejandro Valverde (Spain / Movistar) +2:35"
4. Joaquim Rodriguez (Spain / Katusha) +3:57"
5. Fabio Aru (Italy / Astana) +4:46"
6. Samuel Sanchez (Spain / BMC Racing) +10:07"