Left-arm wrist spinner Tabraiz Shamsi took his first ever five-wicket haul in ODIs to flatten the Sri Lankan batting as South Africa levelled the three-match series at 1-1.
Keshav Maharaj led the side to the comeback victory after they lost the opener and regular skipper Temba Bavuma, who was ruled out of the limited-overs series with a broken thumb.
"I think the boys showed energy, set the tone, had good motivation," said Maharaj. "Fair play to the coach to play three spinners today. We wanted to capitalise and we did."
Malan, though, remained the hero after he battled cramps in his 135-ball knock laced with nine fours and one six.
He built partnerships including a second-wicket stand of 96 with Reeza Hendricks, who made 51, after a 90-minute delayed start to the game.
Malan and Hendricks, who was one of the three changes in South Africa's team for this match, mixed the right dose of caution and aggression and hit seven boundaries between them.
Hendricks raised his third ODI fifty before being bowled by off-spinner Dhananjaya de Silva.
Malan reached his ton in the 43rd over as he put on an attacking partnership of 86 off 54 balls with Heinrich Klaasen.
Pace bowler Dushmantha Chameera broke the stand to send Klaasen, for 43 off 27 balls, trudging back to the pavilion and trapped Malan lbw on the next delivery.
"We started well, but (the) Reeza-Janneman partnership took the game away from us," Sri Lanka skipper Dasun Shanaka said. "Our fielding wasn't up to the standard. Bowling was a concern too."
Sri Lanka's reply was hit by three early wickets including two in one over by pace bowler Kagiso Rabada, who sent back Avishka Fernando for eight and Bhanuka Rajapaksa for nought.
Charith Asalanka tried to help Sri Lanka fight back with his 77. He put on 64 runs for the fifth wicket with Shanaka, who made 30.
Shamsi took down both the batsmen and the innings folded soon after a quickfire 36 from Chamika Karunaratne.
The third and deciding ODI is on Tuesday at the same venue.